Take a look at the Golang source for some examples of table-driven tests. When you need mocking you can usually use interfaces and then define a struct in your test that satisfies the methods of the interface. I'm currently co-authoring a book called Production Go which includes a chapter on testing. That chapter is available in the free sample: https://leanpub.com/productiongo/ and might cover some of your questions…
Ask HN: How to approach testing in Golang?
21–30 of 37 posts
Re: Ask HN: How to approach testing in Golang?
#22Also, consider what you are really testing with mocks. Your implementation, or your mock? You can test without mocks..,
Can you elaborate, specifically on a unit-testing level?
Re: Ask HN: How to approach testing in Golang?
#23The age of Docker means IMO you should avoid mocks. There's no reason not to have a full integration test against your real API especially because you can compile and run test binaries independently.
Not an apologist for unit tests, but I'll bite here: unit tests are still valuable when you have the capacity to run ful integration tests. A unit test can run really fast, for one, it can cover future use cases that don't get covered in integration, it edge cases you don't expect to hit, but are still important to cover in case something happens. I'm sure there are more, good reasons to write unit tests and integrat…
Re: Ask HN: How to approach testing in Golang?
#24I'm not a fan of writing stupid mocks. That doesn't give me much confidence, and spending time on tests is mostly about building ones confidence. I very much prefer running the program end-to-end with defined inputs, and checking its outputs.
Whether that's database, file on disk, or REST api. I write python nosetests that prepare the setup, run golang program, run the checks, and tear down the setup. Sadly, normally it's hard to get golang code coverage for end-to-end tests (aka integration tests). This is what I usually do:
https://blog.cloudflare.com/go-coverage-with-external-tests/
Re: Ask HN: How to approach testing in Golang?
#25Also, consider what you are really testing with mocks. Your implementation, or your mock? You can test without mocks..,
There are some things that are implausible to test without injecting not-production-code. Many of these look like errors (bad inputs, expired sessions, etc.) that should be intelligently handled.
Re: Ask HN: How to approach testing in Golang?
#26 type Database interface {
Users() ([]*User, error)
}
I like to create an explicitly-mocked version of it like this: type MockDatabase struct {
UsersStub func() ([]*User, error)
}
func (m MockDatabase) Users() ([]*User, error) {
if m.UsersStub == nil {
panic("MockDatabase.UsersStub is nil")
}
return (m.UsersStub)()
}
Then you can create the resources you need, and define how they work, at the top of your test: func TestSomething(t *testing.T) {
db := MockDatabase{
UsersStub: func() ([]*User, error) {
...
}
}
// use db here
}
This works well as long as the things you're testing take all the resources they need as parameters.Re: Ask HN: How to approach testing in Golang?
#27I like to use a pattern like this: given a resource defined as an interface: type Database interface { Users() ([]*User, error) } I like to create an explicitly-mocked version of it like this: type MockDatabase struct { UsersStub func() ([]*User, error) } func (m MockDatabase) Users() ([]*User, error) { if m.UsersStub == nil { panic("MockDatabase.UsersStub is nil") } return (m.UsersStub)() } Then you can create the r…
Re: Ask HN: How to approach testing in Golang?
#28I like to use a pattern like this: given a resource defined as an interface: type Database interface { Users() ([]*User, error) } I like to create an explicitly-mocked version of it like this: type MockDatabase struct { UsersStub func() ([]*User, error) } func (m MockDatabase) Users() ([]*User, error) { if m.UsersStub == nil { panic("MockDatabase.UsersStub is nil") } return (m.UsersStub)() } Then you can create the r…
Re: Ask HN: How to approach testing in Golang?
#29Here are a few don'ts:
- don't take interfaces as arguments unless it really makes sense, even if it makes testing easier
- don't use one of the tiny test helper libraries that basically just writes if statements for you
- don't start writing tests until you've already figured out how to organize your logging and errors
One pattern I found helpful was to write an adapter that uses the testing.T.Print* functions as the destination for log.Print* statements in my regular code so that log messages aren't jumbled when running tests in parallel.
Re: Ask HN: How to approach testing in Golang?
#30I like to use a pattern like this: given a resource defined as an interface: type Database interface { Users() ([]*User, error) } I like to create an explicitly-mocked version of it like this: type MockDatabase struct { UsersStub func() ([]*User, error) } func (m MockDatabase) Users() ([]*User, error) { if m.UsersStub == nil { panic("MockDatabase.UsersStub is nil") } return (m.UsersStub)() } Then you can create the r…
I also use that same pattern. I'm very tempted to write a tool to be called by `go generate` to automatically create that kind of mock.
Here's one: