Learning Rails part 3 – Using RSpec
So I’ve been going at this Rails things for a few days now and I learned a lot. One of the things I discovered is how you can make developing applications easier in Rails with the help of some test-driven development techniques.
There’s a lot of stuff you can do to test your application. The lazy method would be to invest your time in manually testing your application. There are however plenty of other options that will save you large amounts of time. I’ve tried a few things myself and I learned that there’s really no need to manually test your rails applications. 99% of all test work can be done fully automated.
In this post I will take you through one of the options and how you can use it to test various aspects of the application. I will also point you to some sources to learn more about the tools.
Building unit tests using RSpec
When you talk about building automated tests to a developer he will think unit tests. Which is probably right for him, since those are the kind of tests usually made by developers. Unit tests are also the first and cheapest line of defense against bugs and general trouble in your software.
Rails has a default testing framework called Unit, but don’t use that. I always delete the test folder in my project containing the Unit based tests.
The reason I don’t like Unit is because it’s way too technical in nature. It’s comparable to NUnit, MSTest, JUnit and all those other frameworks that let you write test methods without any good structure.
A way better alternative is RSpec. The RSpec framework uses a syntax that describes what a class should be doing. It makes for a far better reading experience and is closer to what your product owner would give you as the specification for the software. Below is a short example of an RSpec test:
describe Post do it "validates presence of title" do post = Post.new(:body => "Test", :tags => [".NET","C#"]) post.valid?.should be_false end end
The test tells the API that this test describes the Post class. It describes that Post should validate the presence of a title attribute value.
The cool thing about this is that RSpec is able to document all your tests as well as run them.
How to install RSpec
To get RSpec running in your project, you need to download the RSpec-Rails gem. This gem contains everything you need to get going with RSpec tests. To install the gem, add the following line to your project
group :test do gem 'rspec-rails' end
Save the file and run `bundle install` to update the gem configuration. After you have run the bundle command you need to generate some configuration files by running the following command in the terminal.
rails generate rspec:install
This will generate the necessary configuration to start building RSpec tests in your project.
Writing RSpec tests
Writing RSpec tests is done by creating new ruby files inside the spec folder of your rails project.
For a rails project, the structure of the spec folder looks a bit like this:
- controllers
- models
- helpers
There’s a different folder for each type of component that is tested in the application.
So to write a test for a Post model, you add a new ruby file with the name post_spec.rb in the spec/models folder.
In this file you then specify your test with describe and it method calls like this:
describe [Class] do it "[Some fact]" do [Code to execute the example] end end
You can nest the describe statements to make categories and subcategories in your tests or examples as the makers of RSpec call them.
The subcategories aren’t all that important for smaller components in your application. But once you start testing controllers you will quickly discover that you will need the extra describe calls to keep the examples manageable. Let me demonstrate with a short hypothetical example:
describe PostsController do describe 'GET index' do it 'should return success for authenticated users' do # ... end it 'should redirect to sign_in for unauthenticated users' do # ... end it 'should load all posts' do get 'index' assigns(:posts).should be_true end end describe 'GET new' do # ... end end
This set of examples describes the functionality of a single controller. As controllers are more complex you will need to provide more facts about each individual action on the controller. To keep this manageable you will need an extra describe call to keep all the facts for a single operation together.
Verifying results
RSpec contains a large set of verification options that you can use to check the outcome of methods you invoke on your components. As with a lot of things in ruby, you have them available as bonus methods on your own components
For example, I can validate that something is true by invoking this:
post.valid?.should be_true
The first two keywords belong to my object. I’m calling the valid? method on my post object. The next method I’m invoking is an expectation provided by RSpec. This allows me to check the result returned by the valid? method using one of the matchers provided by RSpec.
A matcher is comparable to an assert in .NET and Java in that it compares an expected value against an actual value. In this case, the return value of valid? should be true. This however isn’t the only matcher that’s available. Here’s a very short list of a number of matchers that you can use
- eq – Matches the value exactly against another value
- be_false – Checks that the value is falsy (Remember javascript, yes, this is where that came from!)
- be_true – Checks that the value is truthy
- be([value]) – Checks that the object identities match between the actual and expected value
- match(/[expression]/) – Uses regex to match the actual value against a pattern
And there’s a lot more you can use. To find out more, check out the documentation on matchers: https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
The should technique isn’t the only way to check the outcome of an operation. There’s also expect. The following example demonstrates this:
expect(post.valid?).to be_true expect { post.save }.to raise_error
The first call to the expect method executes the single method call and returns it value to validate using a matcher. The second call does the same thing, but differently. The thing to keep in mind here is that the second expect catches any errors raised in the code block that is being invoked. So if you want to check that a method does raise an error, you need to use the expect with the curly braces.
Specific test considerations
Although you can write any test with the information in the previous two sections, I’m going to add a little more to that information. Because RSpec will offer additional help when you write examples or tests for a Rails application.
For example, if you add an example file to the spec/controllers you will automatically get support for invoking actions on controllers using the HTTP verbs that you’d normally use in the browser. In fact, each of the examples you write assumes that you will write an example for a single HTTP request to your controller. Within each example you can use get,put,delete,post,patch or any of the other HTTP verbs to invoke the controller and then check the results of the operation. For example, the following is possible in a spec that is placed inside the spec/controllers folder:
describe PostsController do describe 'GET index' do it 'Returns success' do get 'index' # Invoke the action on the controller using the GET verb response.should be_success # Validate the response status end it 'Assigns the posts collection' do # Create some sample data post = Post.create(:title => "Test post", :body => "Test post") get 'index' # Invoke the action on the controller using the GET verb assigns(:posts).should eq [post] # Make sure that the post we created is loaded end end end
The specification is a basic RSpec example set, with an extra subcategory for the index action. Within the first example you see a call to get the index action. This get call can only be done inside a controller example. It uses the rails framework to invoke the index action (Without loading the view for the page!).
After you’ve invoked an action you have two options to validate the results. The first verification you can do is to check the response object. This is the HTTP response returned by the controller and contains the status as well as the template that is going to be rendered by the controller. The second verification you can perform is checking what value the assigned variables are after a controller action is executed. This is done by calling the assigns method with the name of the variable that is going to be assigned. You can save the assigned value to a new variable or check it immediately using the expectations offered by RSpec.
Controller examples are probably the most complex examples you can create with RSpec. When you look at model tests, there’s a lot less going on:
describe Post do it 'validates the presence of title' do post = Post.create(:body => 'Test post', :tags => ['C#','.NET']) post.valid?.should be_false end end
Model classes have no special actions, expectations or matches available to them. These tests are more like unit-tests, which is a lot simpler.
About that database
All Rails applications use some sort of database. This means that your tests will mess up the data in your application’s database. You could try and stub the database, but it’s not recommendable, as that means you have to do a lot of work. There’s an easier and better option called `database_cleaner`
To install the gem, modify your gemfile to look like this:
group :test do gem 'rspec-rails' gem 'database_cleaner' end
Run `bundle install` again to set up the new gem in your project. After you’ve install the gem you can then configure your RSpec to use database_cleaner by adding the following lines to the `RSpec.configure` block in your spec_helper.rb file inside the spec folder.
config.before(:suite) do DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.strategy = :transaction end config.before(:each, :js => true) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end
This will clear the database before each test suite (A single .rb file) by truncating everything out of the database. Before each individual example, the data is restored by rolling back a transaction.
By using a transaction per example you get a fast recovery of the database before executing the next example. If you would use this strategy for a full suite however, you will run into timing problems and memory problems as the transaction could grow out of hand over time as you add more examples. Also, by using the combination of truncation and transactions you can set up data at the start of a suite and keep it until that suite is done. This makes writing tests a lot easier when you need more data.
Adding the lines above to the spec helper is all that you need to do to keep a clean database during your tests.
Running tests
Once you’ve written a number of examples/tests and set up your database cleaner gem, you can execute the RSpec tests by executing the following command on the terminal:
bundle exec rspec
This will execute all the examples in your project. If you want to execute a subset, you can specify which files to run by appending the name of the folder to the command like this:
bundle exec rspec spec/models
Pro tip(tm): Keep in mind that running tests is always done in the test environment. This means that if you haven’t updated your database in the test environment configuration, you will need to do so by executing the following on the terminal:
rake db:migrate RAILS_ENV=test
Integration with Guard
RSpec is really useful for testing stuff on model and controller level in your application. This means that you can use RSpec both for unit tests and integration tests.
In my previous post I showed you how to use Guard to automate some of the command line stuff in your rails project. RSpec can be automated using Guard as well. To use this, add the following to your gemfile:
group :development do
gem ‘guard-rspec’
end
Run `bundle install` and execute the following command on the terminal to configure the RSpec plugin for Guard:
bundle exec guard init rspec
After executing this command you will have additional configuration in your Guardfile to run the RSpec examples when a controller or model is changed. Restart Guard to load the new specification and start running examples as you change your code.
What’s next?
With the RSpec tests up and running, I’m going to build some more bits of my application. So it will take a few days to generate some new material Keep an eye out for the next part!
In the mean time, read this book: The RSpec book
It contains a ton of useful information on how to build proper examples with RSpec and what’s going on behind the scenes.