February 13, 2013
TDD & Mocks - Working Backwards From ExpectationsOn my infamous TDD training workshop, I encourage participants to write the test assertion first and work backwards to the set-up. This is a good way to turn our thinking around, starting with the "what" and working our way back to a "how" that directly supports the "what".
What often throws people is working with mock objects. Because we're not writing explicit assertions using our assert() functions, we may fail to spot that mock tests also make assertions.
In Mockito, for example, we assert that a method should be invoked using verify(). This is the "what" of an interaction test. And it's possible to start there and work our way back, just as we might do with traditional assertions.
In this example, I verify that a reserveSeat() method is called on a mock Performance object, which I intend to inject into a new instance of BoxOffice; the class under test.
Working my way backwards, I write the verify() statement, and than declare Performance and the reserveSeat() method.
Eclipse, which the eagle-eyed among you may have realised is designed for working backwards if we so wish, prompts me to declare a local variable for mockPerformance, which I make of type Performance.
It then prompts me to declare the type Performance. Many mocking tools might require me to make Performance and interface if I intend to mock it in the simplest way. Not so with Mockito. It treats mock classes and interfaces transparently the same (one of the things I like about it - saves me declaring lots of unneeded abstractions.) I declare the class Performance. Eclipse then prompts me to declare the method reserveSeat() on Performance.
Then , Eclipse prompts me to initialise the mockPerformance variable, which I do as a mock object.
As I'm working backwards, the next thing I want to write (having now written my assertion) is the action I want to test.
My test is that, in the execution of boxOffice.reserveSeat(), it should tell my mockPerformance to reserve that seat for the specific credit card number. Again, working back from here, Eclipse prompts me to declare the local variable boxOffice, which in turn leads to me declaring the BoxOffice class and the reserveSeat() method on it.
Then I'm prompted to initialise boxOffice, which I do, injecting a collection of performances from which "Boffoonery" - the name of my mock Performance - should be selected by name.
I'm now prompted to declare the constructor on BoxOffice that accepts a HashMap as the parameter.
Finally, I need to inject my mock Performance into the appropriate slot in the HashMap so it will be found using the name "Boffoonery".
And there is my complete failing interaction test. If I run the test now - always a good idea to see the test fail in the way you expect it should, so you know it's a valid test - it fails with the message: Wanted but not invoked: mockPerformance.reserveSeat('A', 6, '1234')
So we're good to go on writing the simples code to pass that test.
Posted 5 years, 8 months ago on February 13, 2013