Wednesday, April 16, 2008

Mocking and Stubbing in rSpec

I program in Ruby. A lot. I've recently found myself writing applications to provide simple web services to flash applications. We've pretty well standardized on using JSON as our preferred format at Snepo, so I've found it useless attempting to build and delivery anything without using some sort of TDD.

Welcome rSpec to my world, and it adds in a level of communication between myself and the involved parties about what a service should do, that I'm not sure any other framework does. For instance, we can begin specifying behaviors that the controller should return.
I might have a service that finds me all users who've been active in the last 24 hours. So I'd specify, like follows.


describe "Users Controller", "find_recent action" do
it "should find all users who've been active in the last 24 hours" do
c, r = request("/users/find_recent/24")
c.body.should eql(["some json array of elements"])
end
end

But! Here is our first problem with outside in development, we don't have a any methods in a User model yet! Now we could go off and build some, but this behavior might change as we move forward and in any case, we should test those methods with their own tests, this is testing the controller class after all. So why not try to get as much of this working how we want now. Well we can.

Hello Mocks and Stubs!

Mocks and Stubs confused me for a long time. The official definition in rSpec says: Mock objects are imitation objects that give you declarative control over their behaviour in the course of the execution of an example

I found this really confusing so I'll try to explain it, in my own words.
A mock allows you to send a message to an object(including a class, a class is an object after all) that does not yet exist( that is the message/method does not yet exist ). The rSpec framework looks out for the method that your test to call this object, and then returns what you specified in your test. If it is never called it considers that the test has failed. That is, a mock is a test in itself, a stub is not. A stub simply substitutes itself for the real object. So to flesh out the above example with a mock.

describe "Users Controller", "find_recent action" do
it "should find all users who've been active in the last 24 hours" do
@users = mock("A list of users")
User.should_receive(:find_all).with(24).and_return(@users)
c, r = request("/users/find_recent/24")
c.body.should eql(@users.to_json)
end
end

What we are doing here is creating a new mock object(@users) so we have something to return from our mock of the static method find_all. That happens in the line below, we tell rspec that it should_receive a message (:find_all) for a class called User, and when it does receive that message to return the mock object that we created above for the controller to keep on using it.
If we just wanted to stub that, i.e. if we didn't care if it got called, but if it does we'd better return something, we'd use: User.stub!(:find_all).and_return(@users).

There is one small misnomer in that whole mocking stubbing framework in rSpec, and that is @users = mock("A list of users"). This is not a mock, if it's not used rSpec doesn't care, there is no expectation placed on this object, to me this should be something like stub_object, in any case, that's a little gottcha albeit a linguistic one.
Now you see, you can get to it from the outside in this time and you can get all the model method's that you'll need sorted before your write a scrap of code in your models.
Oh and just so you know, your controller method might look something like this( in Merb ).

def find_recent
users = User.find_recent(params[:duration].to_i)
render_text users.to_json
end