5 ms·
How I write unit tests in Go
- jozvolskyef 2y agoI would vote this down if I could for the following reasons: - I prefer state-based TDD as opposed to interaction-based (see https://martinfowler.com/articles/mocksArentStubs.html https://martinfowler.com/articles/mocksArentStubs.html). - I've used both testcontainers and dockertest, and from my experience dockertest is more robust. - The capital T for the outer argument comes across as being hypercorrect. Why would one consider the shadowing of this argument bad?
- kromem 2y agoPosts aren't gospel. It's perfectly ok to take what you like from them and leave the things you don't. Is something that has 7 useful things and 3 things you disagree with merited to be buried from public view because of the 3 things you personally disagree with? I've never really understood this perspective.
- jozvolskyef 2y agoOne day, I will be working with someone who will have read this article when they were learning Go, and we will disagree about these three points. I was hoping someone would add their own opinion about these, so that when it happens, I can go back to this thread and have enough information to decide whether to change my mind or not.
- coffeebeqn 2y agoSo unless I pepper every test and subtest with t.Parallel() it will always run sequentially ? I gotta try that. Also I wish I could just declare that once
- 4death4 2y agoIf you use suites you can run t.Parallel() once for the entire suite.
- rickette 2y agoTests inside one _test.go file are run sequentially by default, but Go does run tests in parallel across packages.
- hdra 2y agowhat about multiple _test.go files belonging to the same package? and how does this work with "sub-packages"?
- kbolino 2y agoYou should wait until you actually need it. Parallel tests can produce interleaved output.
- ashconnor 2y agoI'm far too lazy to write mocks by hand in go. You can generate a mock for a given interface with mockery https://github.com/vektra/mockery https://github.com/vektra/mockery
- randomdata 2y agoPer the dictionary, mock is defined as "not authentic or real, but without the intention to deceive." As that applies to software, a mock is a fully-fledged implementation of an interface, but not the implementation you use under normal use. For example, a mock might be an in-memory database where you normally use a persisted database. Not the real implementation, but does not try to deceive – it is equally functional. Mockery appears to be an assertion library that, bizarrely, moves the assertions into interface implementation. What purpose does it serve? You are going to end up testing implementation details by using that, which is a horrible road to go down.
- ashconnor 2y agoMockery is equally function in the sense that it implements the interface it targets. I wouldn't describe swapping a persisted DB for an in-memory DB mocking personally.
- randomdata 2y ago> Mockery is equally function in the sense that it implements the interface it targets. Consider a function with a key/value database dependency: type DB interface { Get(key string) string Set(key string, value string) } func ContrivedFunction(db DB) string { db.Set("key", "value") return db.Get("key") } And let's test it using mockery: func TestContrivedFunction(t *testing.T) { db := mocks.NewDB(t) if ContrivedFunction(db) != "value" { t.Error("unexpected result") } } Code looks reasonable enough, but then... Epic failure. This should work, at least it should if `db` is a mock. But it does not work. `db` deceived us. Clearly not a mock in any sense of the word. But, okay. It is still something. We will play its game: func TestContrivedFunction(t *testing.T) { db := mocks.NewDB(t) db.Mock.On("Set", "key", "value").Return() db.Mock.On("Get", "key").Return("value") if ContrivedFunction(db) != "value" { t.Error("unexpected result") } } Wonderful. We're back in business. Tests are passing and everything is sunshine and rainbows. But now, the contrived project manager just called and, for contrived reasons, would like to change the organization of record keys: func ContrivedFunction(db DB) string { db.Set("ns:key", "value") return db.Get("ns:key") } Ah, fuck! The test just broke again. But it shouldn't have. The utility of ContrivedFunction – that which is under test – hasn't changed one bit. The DB implementation should have been able to handle this just fine. The DB implementation used in production handles this just fine. This mockery tool is fundamentally broken. But not only is it broken, it doesn't seem to serve a purpose. Why would you even use it?
- tommiegannert 2y agoNice showdown of a bunch of scenarios. I'd add using testing.T.Cleanup for tearing down the testcontainer (or use a TestMain and a deferred if the container is slow and concurrency-safe.)
- kbolino 2y agoFor table-based testing, I have been using map[string]struct{...} for a while now instead of including the test name as a struct field. I have found it improves readability slightly and makes it harder to misname test cases (empty strings stand out more and duplicates won't compile). Also, the shadowing of t in t.Run is intentional. You should not try to work around it. There's no risk of confusion either because you will always use the right one in the right place.
- count_chocula 2y agoi am terrified of clicking on that website link