Monday, August 11, 2008

Using rspec to test actionwebservice with Rails 2

Background:
I'm currently working on an application for a client who requested that web services being served by the app are in WSDL format. In Ruby on Rails (before REST became the de facto standard), this was accomplished by using Actionwebservice, but unfortunately it was removed from the Rails core as of version 2. Luckily, it's still available as a gem, and can be reintegrated with your RoR application by following these instructions: Using Action Web Service with rails 2.0

Problem:
After I got AWS working with my application, the problem of testing these methods arose. Even before AWS was ousted from the Rails core, documentation on AWS testing was sparse and any available material usually ended up referring to this RDOC. The RDOC indicates that testing AWS methods should be similar to functional tests for controllers.

While this may have worked, I wanted to test my web service methods using rspec and Mocha for mocking the model behaviour (instead of having to use fixtures), and also to decouple the controller tests from the model layer.

Solution :)
I'm assuming at this point that the rspec testing framework is working properly with your RoR application, and that you're familiar on how to generate regular controller tests with it.

As a side note, I'm using Mocha for mocking data, which can be installed with "gem install mocha" and adding "config.mock_with :mocha" in your spec_helper.rb file.

Finally, to enable rspec tests to work with your AWS controller, add the following line to the top of your *_controller_spec.rb file:
require 'action_web_service/test_invoke'


With these preparations completed, we can move onto the actual tests. As an example, I had the following authentication method to test:

def authenticate(username, password)
# Authenticate user
@user = User.try_to_login(username, password)

if @user
return @user
else
return nil
end
end

(I removed some code in this method for the purposes of this example, so I'm aware that this could have been done in one line without the need for the if statement)

Where User.try_to_login would return a User object if the login was valid, or nil otherwise. Now in order to test this, we need to make sure that the model logic is clearly separate from the controller logic. This means that User.try_to_login should not actually hit the User model, and instead be mocked in the test.

The following is my rspec test for this method:

describe SysController, "The 'authenticate' method" do

before(:each) do
@user = User.new
end

it "should return a User object for valid users" do
User.stubs(:try_to_login).returns(@user)

result = invoke :authenticate, "valid", "valid"
result.should_not be_nil
result.should be_an_instance_of(User)
end

it "should return nil for invalid users" do
User.stubs(:try_to_login).returns(nil)

result = invoke :authenticate, "invalid", "invalid"
result.should be_nil
end

end


The line "User.stubs(:try_to_login).returns(@user)" is where Mocha works it magic. This line basically intercepts any call for the method "try_to_login" to the User object and modifies its return, all without being affected by any model logic.

Another thing to note is that the actual arguments sent to the authenticate method ("valid", "valid" or "invalid","invalid") in both tests shouldn't have any bearing on the controller logic it's testing since these arguments should only affect the model logic for try_to_login. Instead, the controller is only concerned with the return of the try_to_login method, i.e. whether @user is returned as nil or a User object.

Although this probably wasn't the best example, it can still give you a general idea on how to implement rspec testing with Actionwebservice controllers and using Mocha to mock model behaviour.

1 comment:

Johannes Fahrenkrug said...

Greg,

Thank you so much! This was exactly what I was looking for and even the first hit when googling for "how to test actionwebservice".

Thank you!

- Johannes