Rails Functional testing with Stubs and BDD
One of the things that bothers many people about functional tests with Rails is the seemingly unnecessary duplication of test coverage.
Consider this test, a very common “create new user” functional test:
def test_create post :create, {:user=>{:username => "brian", :password =>"1234", :password_confirmation=>"1234", :email=>"hoganbp@uwec.edu"}} assert assigns(:user) assert_redirected_to :action=>"list" end
This is an example of test-driven development at the controller level. You pass in the POST parameters so that the controller can create the user object and save it to the database. This then triggers the redirect.
But this is really kinda bad. You already know that you can save records, you’ve got unit tests that prove that your validation works. You’re really interested in how the controller responds when it creates a valid record.
Behaviors
Behavioral driven development (BDD) is a technique receiving a lot of attention lately in the Rails community. The idea is to write your tests looking at the behavior you’re trying to test, as opposed to just testing methods.
Take a look at the behavior of the controller. What’s it supposed to do?
When a new user is created successfully, it should go to the list action.
So… let’s rename that scaffolded functional test to
def test_should_redirect_to_list_when_user_is_created end
In BDD, you use words like should to specify what should happen. See, already your test is less ambiguous. Eventually you’re going to have to test that it “should_render_registration_form_when_creation_fails” or something like that.
Stubs
We already know from unit tests we wrote that we can save user records. We don’t need to test that again. Good tests don’t cover more than one piece of functionality anyway. How do we get around to it?
Enter http://rubyforge.org/projects/mocha/. Mocha is a mocking library. You can read more about it in the documentation, but I’ll show you how to use the stubbing features to bypass creating records.
Install mocha.
gem install mocha
Open up your test/test_helper.rb and add
require 'mocha'
to the top of the file.
This simple bit of code added to our test makes it all work:
User.any_instance.stubs(:save).returns(true)
You would read that as “Any instance I create of User should have its save method always return true when I call it.”
That means our functional test becomes
def test_should_redirect_to_list_when_user_is_created User.any_instance.stubs(:save).returns(true) post :create assert_redirected_to :action=>"list" end
Wrapping up
Stubbing helps you decouple your methods, and BDD helps you think about your process rather than your code. I encourage you to read more about both. Keep in mind you still want to test other aspects of your controllers, and sometimes you might want to ensure that certain things work certain ways. For example, you might still need @user to contain real data when you do searches so your views will render properly. This is just an example of the power of stubbing.