This morning Mike helped me work through my first controller-style RSpec test, and the “catch” was that I was doing it for my Sinatra app. Since this was my first test like this, and it is slightly different from a Rails app, I’m going to document a walkthrough to help it sink in.

In my main Sinatra app file I have the following action:

class Foundry < Sinatra::Base

#...other methods omitted...

post "/users" do
@user = DataStore::UserDbInteractor.new(DATA_STRATEGY).create(params)
erb :thankyou
end
end

The only think I really want to test in this action is that the UserDbInteractor is called to create a new user. In Rails I could set up this test and then check that my @user I created is equal to a user I set up in the test, like so:

assigns[:user].should == user

Unfortunately, the ‘assigns’ action is not available in Sinatra, but I can use message expectations just as you would in a Rails test test to assert that a mock UserDbInteractor should receive the “new” and “create” methods- and that’s really most of what I need to test here anyway. I can test the UserDbInteractor explicitly to confirm that the object it returns is what I expect, and I also already have a Cucumber test that fills in a form, posts to “/users” and confirms the output is as expected.

So, here’s how my RSpec test for the "/post" action looks:

describe "Foundry" do
def app
@app ||= Foundry
end

#...some other tests...

it "should send the form values to a new User Interactor and create a user" do
params = {"name" => "Test", "email" => "test@test.com"}
user = :user
interactor = mock(DataStore::UserDbInteractor)
DataStore::UserDbInteractor.should_receive(:new).and_return(interactor)
interactor.should_receive(:create).and_return(user)
post "/users", params = {name: "Test", email: "test@test.com"}
end
end

The first three lines below the “it should…” statement are my set-up, including creating my mock interactor.

The next line is my first message expectation, that DataStore::UserDbInteractor should be called with the “new” method. If I was to change :new to a method that I didn’t have, for example :zebra, I would get the following error message from my failing test:

Failure/Error: DataStore::UserDbInteractor.should_receive(:zebra)#.and_return(interactor)
       (<DataStore::UserDbInteractor (class)>).zebra(any args)
           expected: 1 time
           received: 0 times
     # ./spec/foundry_spec.rb:23:in `block (2 levels) in <top (required)>'

So in this line I want to make sure it receives the call to new and when it does I want it to return the interactor I set up with the and_return method.

If you look back up at my original “/post” method (the first gist) you can see that I have the create method chained on after the new method. So in my second message expectation I’m asserting that the interactor object I created and returned should now have its create method called. Since I’ve returned the new interactor object in the first expectation, and I have a create method setup for it (in a separate file), this second expectation passes too.

The slightly odd thing about this test is that it doesn’t follow the typical context -> action-> assertion pattern that I’m used to in testing. It’s actually my final line that calls the action to “/post” after my assertions, but without it the previous message expectations would fail.

If this is anyone’s first look at this type of test I recommend setting up something similar to it (and you could definitely use some simpler classes and objects) and then just playing around with the spec to see what passes and what fails. Once Mike helped me to get it passing I started moved things around and commenting out lines or sections to see how things would fail, and as usual, the error messages helped explain how I was breaking things.