Watch Those Mocks
I like writing tests. That’s obviously not surprising these days. However when it comes to writing tests, especially in larger applications when you might be interfacing with an external service, it often feels like its just a matter of time before you want to start using mock objects. Mocks are great, but I’ve found that they can lead to some problematic situations if you aren’t careful. One such situation I’ve seen is caused by how you use stub objects in your tests. In Ruby’s mocking framework Mocha, you can do something like this:
my_mock = stub(:foo => "stuff", :bar => "other stuff")
If you aren’t familiar with the above syntax, it simply creates an object which will respond to the methods ‘foo’ and ‘bar’ by returning the strings ’stuff’ and ‘other stuff’ respectively. This is a handy thing to have available. It’s concise, and it is easy to throw around in your tests when you need to quickly stub out an interface. For instance, lets say you have the following class:
class ComplexObject
//....methods and such....
def method_that_does_stuff
//doing some stuff
end
def another_method
//more stuff
end
end
Perhaps ComplexObject is an object that gets returned from some method that interacts with an external service and this object is populated with data from said service. In your test you might not want to actually go to external service, so instead you decide to mock out that interaction. Great. So, to do this you go ahead and put the following in your test:
my_fake_response = stub(:method_that_does_stuff => "Mocked response", :another_method => "foo")
ServiceWrapper.expects(:get_complex_data).returns(my_fake_response)
In other words, you create a stub object which mocks out the interface on the ComplexObject class to simulate its behavior. I’ve seen this numerous times and have been a victim of it myself. However, this is problematic.
If you use a stub object like this, you are opening yourself up to a situation in which your tests would continue to pass even if there were problems in the code. As an example, what if you change the name of one of the methods on the ComplexObject class? Well, with the stub object approach above, any test using that fake stub would continue to pass even though the interface your stubbing does not actually represent the interface of the ComplexObject class anymore. This means that if you were going through a refactoring, it would be possible to miss code that still used the old method names. This is a small example, but even it feels like enough for me to go about this situation differently. Instead, when you mock out your interface you should use an actual instance of the ComplexObject class:
my_fake_response = ComplexObject.new(fake_data)
ServiceWrapper.expects(:get_complex_data).returns(my_fake_response)
This means that, while you’re mocking out the interface to the service, you are still actually using the response object type that the code is really expecting. This way when you change the ComplexObject class, all your tests will reflect those changes even if you’re mocking out certain interactions.
I think that one of the reasons I had leaned toward using stub objects at one point or another was due to the fact might be kind of a pain in the ass to create the actual object type that would be returned (ComplexObject in this case). To avoid that pain in the tests, creating a simple stub just feels nicer sometimes. While it may be easier, I’ve started to feel like doing that will just allow you to hide some of the complexity in your data models. Instead, if it’s hard to create the objects you want in your tests, you should probably face that problem head on and make it easier to write the tests you should be writing to avoid troublesome situations like the one above.