Thursday, May 31, 2007
Testing: Replace Mock with Stub
I often hear the phrase: I remember when I thought Mocks were the silver bullet. In fact, I remember using Mocks like they were the silver bullet. Unfortunately, using Mocks excessively can lead to a test suite that is just as brittle, if not more brittle than using concrete classes as dependencies.
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)
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.
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.
Now imagine that the SqlStatementBuilder needs to be changed to allow a prefix for the table name.
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
A closing quick tip: The stub_everything method also takes a hash.
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.
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
endWhile 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.
endLooking 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.
endNow 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
endUsing 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.endA 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
endUtilizing 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.
Labels: Testing Refactorings
Comments:
<< Home
Don't read this the wrong way I like the idea of stub_everthing and the flexibility it brings with one assertion per test.
Do you ever run into issues with stub_everything. Times where you get false positives or missed tests?
Do 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.
stub_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
Post a Comment
stub_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
<< Home


