As the size and complexity of the applications I have been working on has grown, the importance of writing good unit test has become increasingly apparent. The goal is to have a test suite that is fast and able to withstand changes.
One of the best approaches I have found to writing good unit tests is discussed by Sandi Metz in Practical Object Oriented Design in Ruby and also in a talk she gave at RailsConf in 2013.
She has a rule about testing outgoing command messages that says, “expect to send outgoing command messages”. This means that we should assert that the method actually issues the command message, but we don’t assert the result of that message.
For example, suppose I have a party, and I want users to be able to RSVP:
1 2 3 4 5 |
|
I want to assert that when user.rsvp(party)
is called, the add_guest
message is sent to the party object. Mocks are the perfect tool for testing these command messages because they allow us to test that the correct message is being passed without worrying about how the other object actually deals with that message:
1 2 3 4 5 6 7 8 9 10 |
|
This allows us to test the rsvp
method without having to actually deal with the Party class’ implementation of add_guest
. But what happens if we change the public interface for add_guest
to allow a guest specify how many to friends they plan to bring to the party?
1 2 3 4 5 6 7 8 9 10 11 |
|
Our rsvp
method is now broken because the add_guest
method requires two arguments, but we are only passing it one. Unfortunately our test for rsvp
still passes because our test double does not reflect the actual interface that it is mocking.
To solve this problem I recently started using a mocking framework called Bogus. It ensures that your test doubles have the same interface as the real class.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now when we run the test we receive the following failure:
1 2 3 4 5 6 |
|
Great! Our test double now implements the same interface as the actual Party class and alerts us that our rsvp
method is not passing the correct number of arguments. Let’s update rsvp
:
1 2 3 4 5 |
|
And run our test:
1 2 3 4 5 6 |
|
Whoops! Looks like our stub for add_guest doesn’t implement the correct interface for add_guest
. Let’s update it:
1 2 3 4 5 6 7 8 9 |
|
Another great feature of Bogus is that it supports mocking a duck type. For example, suppose that in addition to parties, we want users to be able to rsvp to dinners. We can implement this with a duck type by creating an add_guest
method for our new Dinner class:
1 2 3 4 5 6 7 8 9 10 11 |
|
And now in our test we can mock our duck type interface:
1 2 3 4 5 6 7 8 9 10 |
|
Mocks are a great way to focus on the messages that your objects are sending when testing. Give them a try and experience the benefits of a faster more resilient test suite.