Comment by lelandbatey

3 months ago

To fully show my hand: What I do not like are mocks which have you outline a series of behaviors/method calls which are expected to be called, typically with specific input parameters, often with methods in order. In python, this looks like setting up Mocks and then using `assert_called` methods. In Go, this means using Gomock-generated structs which have an EXPECT.

A test should not fail when the outputs do not change. In pursuit of this ideal I often end up with fakes (to use Martin Fowler's terms) of varying levels of complexity, but not "mocks" as many folks refer to them.

[0] - https://docs.python.org/3/library/unittest.mock.html#unittes...

[1] - https://pkg.go.dev/github.com/golang/mock/gomock

So this I agree with. Mocks are often used to over-constrain and end up validating that the implementation does not change rather than does not regress.

There are some specific cases, such as validating that caching is working as expected, where it can make sense to fully validate every call. Most of the time, though, this is a pointless exercise that serves mostly to make it difficult to maintain tests.

It can sometimes also be useful as part of writing new code, because it can help validate that your mental model for the code is correct. But it’s a nightmare for maintenance and committing over-constrained tests just creates future burden.

In Fowler terminology I think I tend to use Stubs rather than Mocks for most cases.

Yeah I think true mocks often lead to fragile, hard-to-read tests. A text which expects a bunch of specific interactions with a mock is white-box, whereas a test which tests the state of a fake after the operation is complete is mostly black-box. Then, if your dependency gets updated, only the fake breaks, rather than all of your test expectations.

A particularly complex fake can even be unit-tested, if need be. Of course, if you're writing huge fakes, there's probably something wrong with your architecture, but I feel like good testing practices should give you options even when you're working with poorly architected code.