One of the big reasons I began using Mocks in tests was to avoid cascading failures. However, I quickly learned that cascading failures still occur when using mocks, just in another flavor. Any implementation change required each instance of the mock to be updated to reflect the new implementation. My "solution" was just as bad as my problem.
To resolve this issue I started relying on Stubs. In fact, I found that using both Mocks and Stubs together made my tests more expressive. When I wrote that entry I was using C# in my day job; however, I think the concept transcends languages.
I'm going to use an example similar to the one from the original entry, this time in Ruby. It's completely acceptable to write the test using only mocks. (Mocha used for mocking)
attr_reader :instance, :where
@instance, @where = instance, where
end
"select * from "
end
end
end
While using mocks does verify the behavior of the builder class, that verification is superfluous. In fact, I believe the above test is covering too much. The state based assertion (assert_equals ...) at the end is all that's really necessary. The mocks can be replaced with stubs since the state based assertion is sufficient.
end
Looking at the above example your first instinct might be to combine the tests. An alternative and possibly superior solution is to continue to test the interactions independently, but only pass in stubs that are necessary for each individual interaction. This is where the stub_everything method from Mocha becomes very handy.
end
Now imagine that the SqlStatementBuilder needs to be changed to allow a prefix for the table name.
attr_reader :instance, :where
@instance, @where = instance, where
end
"select * from "
end
end
Using the tests from the last example only one test will fail. This is because the test_where_to_string_is_appended_to_end test method only cares about the interaction with the
where
stub. A quick fix has both our tests running again. Again, the quick fix was isolated to the first test.end
A closing quick tip: The stub_everything method also takes a hash.
a_stub = stub_everything(:int => 1, :str => "string")
a_stub.int # => 1
a_stub.str # => "string"
a_stub.anything_else # => nil
end
Utilizing a stub_everything with a hash you can create stubs that return values you care about and nil for everything else. Used correctly this can also increase the robustness of your test suite.
Don't read this the wrong way I like the idea of stub_everthing and the flexibility it brings with one assertion per test.
ReplyDeleteDo you ever run into issues with stub_everything. Times where you get false positives or missed tests?
Using a stub_everything instance can return false positives. Just last week I was working on a test that used a stub_everything. A path through the method was followed if the stub_everything returned false (which it did, since it returned nil). I happened to notice it was giving a false positive before a bug was ever exposed because of it, but it could have very easily been missed.
ReplyDeletestub_everything isn't a silver bullet. And, there's always risk in using something that has the potential to be powerful. It's a balancing act.
Cheers, Jay
Thanks for the reply
ReplyDelete/dustin