TDD Constructors Golang
Even though there are a few posts on this, I haven't found one with much substance. So hopefully a few people will share opinions on this.
One thing holding me up from having a true TDD workflow is that I can't figure out a clean way to test things that have to connect to networked services like database.
For example:
type DB struct {
conn *sql.DB
}
func NewDB(URL string) (*DB, err) {
conn, err := sql.Open("postgres", URL)
if err != nil {
return nil, err
}
}
I know I could pass the sql connection to NewDB, or directly to the struct and assign it to an interface that has all the methods I need, and that would be easily testable. But somewhere, I'm going to have to connect. The only way to test this that I've been able to find is...
var sqlOpen = sql.Open
func CreateDB() *DB {
conn, err := sqlOpen("postgres", "url...")
if err != nil {
log.Fatal(err)
}
dataBase = DB{
conn: conn
}
}
Then in the test you swap out the sqlOpen function with something that returns a function with the same signature that will give an error for one test case and not give an error for another. But this feels like a hack, especially if you're doing this for several functions in the same file. Is there a better way? The codebase I'm working with has a lot of functions in packages and network connections. Because I'm struggling to test things in clean way, it's driving me away from TDD.
sql go tdd
add a comment |
Even though there are a few posts on this, I haven't found one with much substance. So hopefully a few people will share opinions on this.
One thing holding me up from having a true TDD workflow is that I can't figure out a clean way to test things that have to connect to networked services like database.
For example:
type DB struct {
conn *sql.DB
}
func NewDB(URL string) (*DB, err) {
conn, err := sql.Open("postgres", URL)
if err != nil {
return nil, err
}
}
I know I could pass the sql connection to NewDB, or directly to the struct and assign it to an interface that has all the methods I need, and that would be easily testable. But somewhere, I'm going to have to connect. The only way to test this that I've been able to find is...
var sqlOpen = sql.Open
func CreateDB() *DB {
conn, err := sqlOpen("postgres", "url...")
if err != nil {
log.Fatal(err)
}
dataBase = DB{
conn: conn
}
}
Then in the test you swap out the sqlOpen function with something that returns a function with the same signature that will give an error for one test case and not give an error for another. But this feels like a hack, especially if you're doing this for several functions in the same file. Is there a better way? The codebase I'm working with has a lot of functions in packages and network connections. Because I'm struggling to test things in clean way, it's driving me away from TDD.
sql go tdd
add a comment |
Even though there are a few posts on this, I haven't found one with much substance. So hopefully a few people will share opinions on this.
One thing holding me up from having a true TDD workflow is that I can't figure out a clean way to test things that have to connect to networked services like database.
For example:
type DB struct {
conn *sql.DB
}
func NewDB(URL string) (*DB, err) {
conn, err := sql.Open("postgres", URL)
if err != nil {
return nil, err
}
}
I know I could pass the sql connection to NewDB, or directly to the struct and assign it to an interface that has all the methods I need, and that would be easily testable. But somewhere, I'm going to have to connect. The only way to test this that I've been able to find is...
var sqlOpen = sql.Open
func CreateDB() *DB {
conn, err := sqlOpen("postgres", "url...")
if err != nil {
log.Fatal(err)
}
dataBase = DB{
conn: conn
}
}
Then in the test you swap out the sqlOpen function with something that returns a function with the same signature that will give an error for one test case and not give an error for another. But this feels like a hack, especially if you're doing this for several functions in the same file. Is there a better way? The codebase I'm working with has a lot of functions in packages and network connections. Because I'm struggling to test things in clean way, it's driving me away from TDD.
sql go tdd
Even though there are a few posts on this, I haven't found one with much substance. So hopefully a few people will share opinions on this.
One thing holding me up from having a true TDD workflow is that I can't figure out a clean way to test things that have to connect to networked services like database.
For example:
type DB struct {
conn *sql.DB
}
func NewDB(URL string) (*DB, err) {
conn, err := sql.Open("postgres", URL)
if err != nil {
return nil, err
}
}
I know I could pass the sql connection to NewDB, or directly to the struct and assign it to an interface that has all the methods I need, and that would be easily testable. But somewhere, I'm going to have to connect. The only way to test this that I've been able to find is...
var sqlOpen = sql.Open
func CreateDB() *DB {
conn, err := sqlOpen("postgres", "url...")
if err != nil {
log.Fatal(err)
}
dataBase = DB{
conn: conn
}
}
Then in the test you swap out the sqlOpen function with something that returns a function with the same signature that will give an error for one test case and not give an error for another. But this feels like a hack, especially if you're doing this for several functions in the same file. Is there a better way? The codebase I'm working with has a lot of functions in packages and network connections. Because I'm struggling to test things in clean way, it's driving me away from TDD.
sql go tdd
sql go tdd
edited Nov 16 '18 at 1:50
Dmitry Harnitski
3,86311834
3,86311834
asked Nov 16 '18 at 0:31
A.RowdenA.Rowden
113
113
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
Typical business application has A LOT of logic in queries. We significantly decrease testing coverage and leave room for regression errors if they are not tested. So, mocking DB repositories is not the best option. Instead, we can mock database itself and test how we work with it on SQL level.
Below are sample code using DATA-DOG/go-sqlmock, but there could be other libraries that mock sql databases.
First of all, we need to inject sql connection into our code. GO sql connection is a misleading name and it is actually connections pool, not just single DB connection. That is why, it is make sense to create single *sql.DB
in your composition root and reuse in your code even if you do not write tests.
Sample below shows how to mock web service.
At the beginning, we need to create new handler with injected connection:
// New creates new handler
func New(db *sql.DB) http.Handler {
return &handler{
db: db,
}
}
Handler code:
type handler struct {
db *sql.DB
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// some code that loads person name from database using id
}
Unit Test that code that mocks DB. It uses stretchr/testify for assertions :
func TestHandler(t *testing.T) {
db, sqlMock, _ := sqlmock.New()
rows := sqlmock.NewRows(string{"name"}).AddRow("John")
// regex is used to match query
// assert that we execute SQL statement with parameter and return data
sqlMock.ExpectQuery(`select name from person where id = ?`).WithArgs(42).WillReturnRows(rows)
defer db.Close()
sut := mypackage.New(db)
r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
w := httptest.NewRecorder()
sut.ServeHTTP(w, r)
// make sure that all DB expectations were met
err = sqlMock.ExpectationsWereMet()
assert.NoError(t, err)
// other assertions that check DB data should be here
assert.Equal(t, http.StatusOK, w.Code)
}
Our test asserts simple SQL statement against DB. But with go-sqlmock
it is possible to test all CRUD operations and database transactions.
Test above still has one weak point. We tested that our SQL statement is executed from code, but we did not test if it works against our real DB. That issue cannot be solved with unit tests. The only solution is integration test against real DB.
We are in better position now though. Out business logic is already tested in unit tests. We do not need to create lots of integration tests to cover different scenarios and parameters, instead we need to have just one test per query to verify SQL syntax and match to our DB schema.
Happy testing!
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least foroauth
to reuse tokens.
– Dmitry Harnitski
Nov 16 '18 at 17:21
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53329767%2ftdd-constructors-golang%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Typical business application has A LOT of logic in queries. We significantly decrease testing coverage and leave room for regression errors if they are not tested. So, mocking DB repositories is not the best option. Instead, we can mock database itself and test how we work with it on SQL level.
Below are sample code using DATA-DOG/go-sqlmock, but there could be other libraries that mock sql databases.
First of all, we need to inject sql connection into our code. GO sql connection is a misleading name and it is actually connections pool, not just single DB connection. That is why, it is make sense to create single *sql.DB
in your composition root and reuse in your code even if you do not write tests.
Sample below shows how to mock web service.
At the beginning, we need to create new handler with injected connection:
// New creates new handler
func New(db *sql.DB) http.Handler {
return &handler{
db: db,
}
}
Handler code:
type handler struct {
db *sql.DB
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// some code that loads person name from database using id
}
Unit Test that code that mocks DB. It uses stretchr/testify for assertions :
func TestHandler(t *testing.T) {
db, sqlMock, _ := sqlmock.New()
rows := sqlmock.NewRows(string{"name"}).AddRow("John")
// regex is used to match query
// assert that we execute SQL statement with parameter and return data
sqlMock.ExpectQuery(`select name from person where id = ?`).WithArgs(42).WillReturnRows(rows)
defer db.Close()
sut := mypackage.New(db)
r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
w := httptest.NewRecorder()
sut.ServeHTTP(w, r)
// make sure that all DB expectations were met
err = sqlMock.ExpectationsWereMet()
assert.NoError(t, err)
// other assertions that check DB data should be here
assert.Equal(t, http.StatusOK, w.Code)
}
Our test asserts simple SQL statement against DB. But with go-sqlmock
it is possible to test all CRUD operations and database transactions.
Test above still has one weak point. We tested that our SQL statement is executed from code, but we did not test if it works against our real DB. That issue cannot be solved with unit tests. The only solution is integration test against real DB.
We are in better position now though. Out business logic is already tested in unit tests. We do not need to create lots of integration tests to cover different scenarios and parameters, instead we need to have just one test per query to verify SQL syntax and match to our DB schema.
Happy testing!
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least foroauth
to reuse tokens.
– Dmitry Harnitski
Nov 16 '18 at 17:21
add a comment |
Typical business application has A LOT of logic in queries. We significantly decrease testing coverage and leave room for regression errors if they are not tested. So, mocking DB repositories is not the best option. Instead, we can mock database itself and test how we work with it on SQL level.
Below are sample code using DATA-DOG/go-sqlmock, but there could be other libraries that mock sql databases.
First of all, we need to inject sql connection into our code. GO sql connection is a misleading name and it is actually connections pool, not just single DB connection. That is why, it is make sense to create single *sql.DB
in your composition root and reuse in your code even if you do not write tests.
Sample below shows how to mock web service.
At the beginning, we need to create new handler with injected connection:
// New creates new handler
func New(db *sql.DB) http.Handler {
return &handler{
db: db,
}
}
Handler code:
type handler struct {
db *sql.DB
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// some code that loads person name from database using id
}
Unit Test that code that mocks DB. It uses stretchr/testify for assertions :
func TestHandler(t *testing.T) {
db, sqlMock, _ := sqlmock.New()
rows := sqlmock.NewRows(string{"name"}).AddRow("John")
// regex is used to match query
// assert that we execute SQL statement with parameter and return data
sqlMock.ExpectQuery(`select name from person where id = ?`).WithArgs(42).WillReturnRows(rows)
defer db.Close()
sut := mypackage.New(db)
r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
w := httptest.NewRecorder()
sut.ServeHTTP(w, r)
// make sure that all DB expectations were met
err = sqlMock.ExpectationsWereMet()
assert.NoError(t, err)
// other assertions that check DB data should be here
assert.Equal(t, http.StatusOK, w.Code)
}
Our test asserts simple SQL statement against DB. But with go-sqlmock
it is possible to test all CRUD operations and database transactions.
Test above still has one weak point. We tested that our SQL statement is executed from code, but we did not test if it works against our real DB. That issue cannot be solved with unit tests. The only solution is integration test against real DB.
We are in better position now though. Out business logic is already tested in unit tests. We do not need to create lots of integration tests to cover different scenarios and parameters, instead we need to have just one test per query to verify SQL syntax and match to our DB schema.
Happy testing!
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least foroauth
to reuse tokens.
– Dmitry Harnitski
Nov 16 '18 at 17:21
add a comment |
Typical business application has A LOT of logic in queries. We significantly decrease testing coverage and leave room for regression errors if they are not tested. So, mocking DB repositories is not the best option. Instead, we can mock database itself and test how we work with it on SQL level.
Below are sample code using DATA-DOG/go-sqlmock, but there could be other libraries that mock sql databases.
First of all, we need to inject sql connection into our code. GO sql connection is a misleading name and it is actually connections pool, not just single DB connection. That is why, it is make sense to create single *sql.DB
in your composition root and reuse in your code even if you do not write tests.
Sample below shows how to mock web service.
At the beginning, we need to create new handler with injected connection:
// New creates new handler
func New(db *sql.DB) http.Handler {
return &handler{
db: db,
}
}
Handler code:
type handler struct {
db *sql.DB
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// some code that loads person name from database using id
}
Unit Test that code that mocks DB. It uses stretchr/testify for assertions :
func TestHandler(t *testing.T) {
db, sqlMock, _ := sqlmock.New()
rows := sqlmock.NewRows(string{"name"}).AddRow("John")
// regex is used to match query
// assert that we execute SQL statement with parameter and return data
sqlMock.ExpectQuery(`select name from person where id = ?`).WithArgs(42).WillReturnRows(rows)
defer db.Close()
sut := mypackage.New(db)
r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
w := httptest.NewRecorder()
sut.ServeHTTP(w, r)
// make sure that all DB expectations were met
err = sqlMock.ExpectationsWereMet()
assert.NoError(t, err)
// other assertions that check DB data should be here
assert.Equal(t, http.StatusOK, w.Code)
}
Our test asserts simple SQL statement against DB. But with go-sqlmock
it is possible to test all CRUD operations and database transactions.
Test above still has one weak point. We tested that our SQL statement is executed from code, but we did not test if it works against our real DB. That issue cannot be solved with unit tests. The only solution is integration test against real DB.
We are in better position now though. Out business logic is already tested in unit tests. We do not need to create lots of integration tests to cover different scenarios and parameters, instead we need to have just one test per query to verify SQL syntax and match to our DB schema.
Happy testing!
Typical business application has A LOT of logic in queries. We significantly decrease testing coverage and leave room for regression errors if they are not tested. So, mocking DB repositories is not the best option. Instead, we can mock database itself and test how we work with it on SQL level.
Below are sample code using DATA-DOG/go-sqlmock, but there could be other libraries that mock sql databases.
First of all, we need to inject sql connection into our code. GO sql connection is a misleading name and it is actually connections pool, not just single DB connection. That is why, it is make sense to create single *sql.DB
in your composition root and reuse in your code even if you do not write tests.
Sample below shows how to mock web service.
At the beginning, we need to create new handler with injected connection:
// New creates new handler
func New(db *sql.DB) http.Handler {
return &handler{
db: db,
}
}
Handler code:
type handler struct {
db *sql.DB
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// some code that loads person name from database using id
}
Unit Test that code that mocks DB. It uses stretchr/testify for assertions :
func TestHandler(t *testing.T) {
db, sqlMock, _ := sqlmock.New()
rows := sqlmock.NewRows(string{"name"}).AddRow("John")
// regex is used to match query
// assert that we execute SQL statement with parameter and return data
sqlMock.ExpectQuery(`select name from person where id = ?`).WithArgs(42).WillReturnRows(rows)
defer db.Close()
sut := mypackage.New(db)
r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
w := httptest.NewRecorder()
sut.ServeHTTP(w, r)
// make sure that all DB expectations were met
err = sqlMock.ExpectationsWereMet()
assert.NoError(t, err)
// other assertions that check DB data should be here
assert.Equal(t, http.StatusOK, w.Code)
}
Our test asserts simple SQL statement against DB. But with go-sqlmock
it is possible to test all CRUD operations and database transactions.
Test above still has one weak point. We tested that our SQL statement is executed from code, but we did not test if it works against our real DB. That issue cannot be solved with unit tests. The only solution is integration test against real DB.
We are in better position now though. Out business logic is already tested in unit tests. We do not need to create lots of integration tests to cover different scenarios and parameters, instead we need to have just one test per query to verify SQL syntax and match to our DB schema.
Happy testing!
answered Nov 16 '18 at 1:49
Dmitry HarnitskiDmitry Harnitski
3,86311834
3,86311834
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least foroauth
to reuse tokens.
– Dmitry Harnitski
Nov 16 '18 at 17:21
add a comment |
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least foroauth
to reuse tokens.
– Dmitry Harnitski
Nov 16 '18 at 17:21
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
Thank you. I agree with testing the SQL syntax and that's a good approach for SQL specifically. But what about other networked services like web-sockets, messaging queues, etc.? Is there a better way to test past the connection functions than making them global and swapping them out at runtime in the test?
– A.Rowden
Nov 16 '18 at 16:42
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least for
oauth
to reuse tokens.– Dmitry Harnitski
Nov 16 '18 at 17:21
@A.Rowden You can mock calls to external web services -stackoverflow.com/a/53231951/1420332. It makes sense to reuse Http client at least for
oauth
to reuse tokens.– Dmitry Harnitski
Nov 16 '18 at 17:21
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53329767%2ftdd-constructors-golang%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown