What was a Classic Tester Became a Mockist…
Saturday, November 26th, 2011Today I had an interesting experience which I’d like to share with my friends. No, I didn’t discover my dark side, I knew that before. What I found out is that mocking as a test method comes in handy once you get it.
Why Don’t You Use RSpec?
I’ve never been an RSpec fan. Not that I don’t like the BDD-approach, which I do appreciate, I was always scared about its internal complexity. I happened to stay with MiniTest, and this has nothing – and i mean nothing – to do with the opinion of Mr. Rails.
Now, I don’t say that RSpec sucks and is too complex. This would be bullshit and I might even soon switch to using this powerful framework (just to make my friend Justin happy) but right now, this test framework still is too powerful for me.
1 2 3 4 5 6 7 8 9 10 | class StringTest < MiniTest::Spec describe "String" do before do @string = "Be nice." end describe "#to_xml" do it "returns a string" do assert_kind_of String, @string.to_xml end |
That’s pure MiniTest code, included in Ruby 1.9 and working out-of-the-box.
Testing with Assertions Is Not A Crime!
What MiniTest::Spec gives me is basically a BDD interace for classical testers. I say classic as I learned that testers not using expectations are called classic. Old school. Yo.
I can use the keyword methods describe and it to phrase my requirements to the object (line 2 and 8). Before and after hooks provide convenient setup and teardown blocks (line 3).
However, I still use assertions rather than matchers as found in RSpec. I could use matchers, though. MiniTest provides these.
it "returns a string" do @string.to_xml.must_be_kind_of String end
Wow, this is “better readable” in many people’s opinion. I still prefer assertions. But that’s not the point of this essay. What I was going to talk about is mocking. Right, mocking.
Who needs Mocking?
Now let’s say my #to_xml method accepts an additional parameter to configure the behaviour. We’d have to test that, too.
it "accepts the uppercase option" do assert_equal @string.to_xml(uppercase: true), "<STRING>BE NICE.<STRING>" end
This could go on forever, and we test all the features that the method provides in numerous test cases – or its – uppercase, lowercase, reverse, whatever.
Until now, I’ve been testing only using state verification. I run the method and test the state of my string instance afterwards using assertions. This is called state verification.
Purposeless, I now decide that my app needs another method. Here’s the implementation.
class String def to_exemel(options={}) log "stupid phonetic alias called" to_xml(options) end
I told you, it doesn’t make any sense. What I want to point out is, the method #to_exemel delegates its arguments to the #to_xml method, which we already tested thoroughly.
Expect the Unexpected!
Testing the logging makes sense, I already use mocking here, as I mock the #log method. More on that in a minute.
1 2 3 4 5 6 | describe "#to_exemel" do it "delegates to #to_xml" do String.any_instance.expects(:log) @string.to_exemel end |
However, testing all the behaviour of #to_xml again doesn’t. Here, I found out that mocking is cool, too.
1 2 3 4 5 6 7 8 | describe "#to_exemel" do it "delegates to #to_xml" do @string. expects(:to_xml). with(:uppercase => false) @string.to_exemel(:uppercase => false) end |
First, we define expectations: In the ongoing test case, the #to_xml method must be called on the @string instance along with the specified options hash (line 3-5).
I then call the actual method which is subject of our test. The method will be run – and the mocking framework internally checks, if the #to_xml method is really invoked the way we want it (line 7). If it doesn’t, the test will fail!
I Never Knew It’s Called Behaviour Verification!
What we do here is behaviour verification – we no longer have an assertion checking the final string state (we could do that additionally) but set up an expectation, which changes the workflow of our test slightly.
- I saved a lot of lines of test code since I do not assert all of
#to_xml’s features in the#to_exemelmethod, again. This is cool.
- Nevertheless, after mocking my test case includes a lot of knowledge about internal implementation. The test now’s aware that
#to_exemelforwards to#to_xml. Changing this implementation detail within the class will break the expectation tests, although the second method might still work from the public perspective.
It seems to be a matter of careful choice when to use mocking, and when not. I for myself still lack the experience to make smart assumptions here. However, I can call now myself a Mocker according to Martin Fowler’s great post, which you should definitely read if you haven’t, yet!
Mocking frameworks?
I found mocha to be a very simple to use mocking framework integrating seamless into MiniTest. Never peeked at the source, though. RSpec comes with a powerful mocking functionality as well, which most people might know, use and appreciate.
Outsights?
The example was stupid, I hope I still could point out the difference between status verification and behaviour verification in this short post. Let me know what you think about it. Good nite, and Mock’n‘Roll!
