Tuesday 11 February 2014

Mocking and stubbing

In Rails, you probably have already dealt with the automatically generated Rspec files, that files show you the basic Test Driven-development features, but in the real world things can be more complicated, likely you've read about mocks and stubs as ways to make better Unit Test, but is not clear what they are and how use them. First I will try a definition:

A Stub is a Unit Test Technic that creates a minimal Object, this Object has the minimal attributes and methods that are required for the Test-targeted class to returns a specified result. In the other hand a Mock is a kind of stub with assertions that one or several methods get called. In other words: Stubs are "dead" simulations of Objects that we use to check if the method that we coded returns the value what we expect. Stubs are "blind" to the behaviour of our classes, they just are used to make calls to the methods in the class that we wanna test. Mocks in the other hand are Stubs but they test the behaviour, they have expectations and make assertions about the way that the methods in our class are used, it is: when, how and how many times a method is called.

Stubbing

You have a class called "MakeACake" with three methods in it: MixIngredients(), PutInOven() and Cook(), the Cook() method sets the data inside the class and calls first the MixIngredients() method and then PutInOven() method. If all is OK, the Cook() method returns the boolean 'true'.

MakeACake class needs several libraries to work properly: Milk, Eggs and Sugar. MakeACake class needs to call the method getQuantity() in those classes.

You want to create a Unit Test for your MakeACake class to be sure that it works in the right way. So you should write something like this:

milk = stub("my_Milk") # create stub objects
sugar = stub("my_Sugar")
eggs = stub("my_Eggs")

milk.stubs(:getQuantity).("1 litter") # stubs setQuantity method
sugar.stubs(:getQuantity).("0.5 kgms.")
eggs.stubs(:getQuantity).(2)

mac = MakeACake.new() # creates the object you wanna test
result = mac.Cook(milk, sugar, eggs) # pass the stubbed objects
assert_equal(true, result) # check if all was OK

This test stubs some objects and their methods, then pass the objects to the class MakeACake and finally the assert_equal() Rspec method checks that Cook() method return "true". If so, congratulations!! your class have passed its Unit Test.

But, why I wanna stub things in first place? You want to make stubs for some of these reasons:

  • Incomplete code. Suppose you are in Argentina and you want to start the code of the MakeACake class, but the developer in Costa Rica is not finishing the Eggs and Milk, libraries until the next week, you know for the UML diagrams that those libraries have a getQuantity() method. So you can stub those methods and start working in your class code.
  • Independecy. As the name "Unit Test" suggest, tests must be decoupled, in the example, stubbing the libraries (Milk, Eggs and Sugar) allows you to test MakeACake independently of the rest of the code.
  • Hard replication. Some states are difficult to replicate, for instance the method CheckNetworkFailure() can only be tested under a real failure, in this cases is better Mock an Object.
  • Velocity. Tests can take over 30 seconds: unacceptable. Stubs and mockings allow you brake the tests in smaller and fastest pieces of code.

But take note about something: this test don't know anything and don't test anything about the behaviour of the MakeACake class. Stubs are just "dead" simulations of methods in order to simulate the real-world data that a class needs to works properly.

In some way this is OK, Test Driven Development is about testing results, the return of the methods, not about testing the code itself. We don't must care about how a method do the things, we use TDD just to be sure it returns the correct value.

Mocking

But what about behaviour? Sometimes you wanna be sure that some classes do things in some way and not just get the final result of a method. At given scenarios is necessary to test a class considering its state and its sequence.

If I wanna mock the Egg class to be sure that the getQuantity() method is called at least once when MakeACake is used, I would need a code like this:

Egg = mock("my_egg")
Egg.any_instance.expects(:getQuantity).once.returns(2)