3 ms·
An honest answer, tons of tests with mocks are really frustrating to find. Anything that simply “must” be mocked is better served by returning a “command patte
by ramses0 3y ago
An honest answer, tons of tests with mocks are really frustrating to find.
Anything that simply “must” be mocked is better served by returning a “command pattern” response.
eg: return new DoGood() || new DoBad(), and separately: TestableIsGoodOrBad()
Specifically for your “error from a dependency” situation, I’m of the mind that a function consisting of:
function foo() {
xyz = PrepFoo(...)
try {
response = DoFoo(xyz)
result = Process( response )
} except {
HandleError()
}
}
...that’s the three lines of code that I’m spending my “not 100% code coverage” on. Each sub-function is 1000% testable without mocks, and the combination of functions is a “secure, logic-less composition”, with little test value.
Basically, if you write your code that way you _never_ need mocks, and generally “mocks are dumb” because saying “Mock( DoRequest( FakeRequest() ), FakeResponse() )” is just wishful thinking compared to a properly factored codebase with _real_ non-mocked, significant, functional-only components.
- scubbo 3y agoOh interesting. So in a sense you are passing the "dependency's response" as a parameter _to_ `Process`, and thus removing the need to mock a dependency in order to induce an erroring dependency-response - you can instead _create_ a payload that resembles an erroring dependency-response, and then directly test "when `Process` is called with `BadPayload`, it should respond as follows". Neat - thanks!
- scubbo 3y agoHmm - but hang on, though. Doesn't that just bring the problem up a level? This setup assumes that you have "shallow" logic: that `Process` will not call any other methods, and especially that it won't call any dependency services. While it's possible to unit test `DoFoo` and `Process` independently, you've now given up the ability to unit test `foo` itself - more specifically, you've given up the ability to determine the behaviour of `foo` in the presence of arbitrary behaviour from code which is calls (since specifying `DoFoo`'s behaviour would be mocking). I didn't initially follow what you meant by "that’s the three lines of code that I’m spending my “not 100% code coverage” on", but in the context of this realization, I'm guessing you're saying something like "when my code has to call external dependencies, I wrap the call in this ~3-line pattern, which allows me to directly test the behaviour of each of the components of the call, and I'm comfortable not testing their integration together because it's so straightforward"? Or are you saying that this Command Pattern should be applied _every_ time one calls another function? If so - I guess that works, but I'm back to wondering what that gains? After all, both mocking and this Command Pattern serve to create tests which assert "when my code encounters situation X, it responds in fashion Y" - the difference is just on the "direction of encountering" (mocking controls "when my code calls other code and receives X", Command Pattern controls "when my code is passed Command X"). Don't they both have the same strengths (ability to induce arbitrary situations for the code-under-test to deal with) and weaknesses (if the developer's beliefs about the situations in which X arises are wrong, the tests are asserting on incorrect behaviour; if the business case changes, especially if the type of X changes, then the tests need to be rewritten to match the new situation)?