7 RSpec Practices Every Ruby on Rails Developer Should Know
1 August 2014
Here at Exygy, we place strong emphasis on testing to counter the bugs and logical conflicts that arise from scaling applications. This shouldn’t be strange – everybody knows it’s important to test your code. Just as there are practical coding patterns, there are also pragmatic testing patterns.
These patterns are necessary in maintaining test readability, while reducing clutter and test processing times. Today, I want to share some tips and some general good practices that will make your RSpec experience more robust, and hopefully less painful.
1. Lose the should!
No I’m not talking about shoulda matchers here, I’m talking about your test descriptions here:
it "should create a new user" do .. end it "should assign the current user" do .. end it "should redirect to the posts path" do .. end
This is a very common mistake, and I’ve caught myself doing this many times before. Technically the descriptions are correct, but when you run the entire test suite and you have lots of descriptions starting with “it should”, it becomes really difficult to read. It is better to use third person in the present tense. Instead:
it "creates a new user" do .. end it "assigns the current user" do .. end it "redirects to the posts path" do .. end
Straight to the point, and no filler.
2. Write out all your expectations first.
When you begin to write out your describe blocks, it’s a good idea to write out all the different expectations of your code before you actually write the tests. So for example – I’m about to write up my controller’s create code:
describe "POST create" do it "creates a new post for the current user" it "prevents a user from creating a duplicate post" it "redirects to the posts path" end
When you are following a TDD approach, writing out all the expectations will force you to really spec out your code and force you to ask – what is this code block really going to do? In true TDD fashion, list off your expectations/assertions to guide yourself through the coding.
3. Keep one assertion per block.
Far too often I see test blocks getting loaded up with expectations:
describe "GET #new" do it "creates a new user" do set current_user get :new expect(assigns(:user)).to be_a_new(User) expect(User.count).to eq(0) expect(response).to redirect_to(videos_path) end end
If your code looks like that, then it’s likely that you have a very generalized description for your test block. Jamming in expectations can be tempting to do, because you’ve taken the time to accurately set up your testing environment and you don’t want to repeat the setup. However, you’re really trading off the readability of your tests. Instead:
it "assigns a new user instance to @user" do expect(assigns(:user)).to be_a_new(User) end it "creates a new User object in the database" do expect(User.count).to eq(0) end it "redirects to the videos path" do expect(response).to redirect_to(videos_path) end
Technically these blocks could have been further reduced to one line statements, but you get the picture. Now, it’s easier to read and it’s clear where your test assertions are happening.
4. Before and Let are your friends.
Keep it DRY. We’ve heard this mantra countless times. But how do we apply it to RSpec? Let’s see here:
describe "POST create" do it "creates a new post" do bob = Fabricate(:user) Category.create(title:"News") post :create, id: bob.id expect(Post.count).to eq(1) end it "blocks user from posting a duplicate" do bob = Fabricate(:user) Category.create(title:"News") post :create, id: bob.id expect(Post.count).to eq(0) end end
Whoa, we got a bit of repetition going on there. Let’s bring in before to specify what code we want to be ran before each test block and let’s bring in let to create a lazy-evaluated variable.
describe "POST create" do before { Category.create(title:"News") } let(:bob) { Fabricate(:user) } it "creates a new post" do post :create, id: bob.id expect(Post.count).to eq(1) end it "blocks user from posting a duplicate" do post :create, id: bob.id expect(Post.count).to eq(0) end end
…much better!
5. build() instead of create() whenever possible.
Why? What’s the difference between the two methods? The create() method will hit the database, and actually persist the model instance. In contrast, the build() method will simply keep it in memory. This optimization makes a big difference when you have a lot of tests to run through, as writing to the database can quickly suck up run-time.
When should you use create() over build()? Well, a good rule of thumb is to use create() only if object persistence is really necessary. For instance, if your test will be querying the database (i.e testing authentication) then yes, create() would be necessary. Otherwise, stick to build() whenever possible.
6. save_and_open_page is slick.
Yes, this is for Capybara, but I wanted to sneak this one in. When you’re having problems in Capybara and you can’t figure out why your test is failing – throw in save_and_open_page in the line before your broken code to jump into what your test is seeing.
describe "when visiting profile page" do it "renders the correct profile page" do visit edit_user_path(user) fill_in "Name", with: user.name fill_in "Password", with: user.password save_and_open_page click_button "Log in!" end end
As you would expect, save_and_open_page actually saves the page that your current test sees – in this case I would see what the page looks like before the login button was even clicked. Keep in mind – when debugging, you need to call save_and_open_page before the test failure actually happens, otherwise it won’t work properly.
7. binding.pry can be used in Capybara too!
Occasionally, save_and_open_page isn’t good enough because it generates a saved page which has minimal HTML and CSS assets. What if you want to know what your page looks like, with all of its CSS and Javascript assets during test failure? Well first, throw the pry gem into your Gemfile first. And now let’s take the previous code and throw binding.pry into the mix:
describe "when visiting profile page" do it "renders the correct profile page" do visit edit_user_path(user) fill_in "Name", with: user.name fill_in "Password", with: user.password puts current_url require 'pry'; binding.pry click_button "Log in!" end end
When you run the test, the URL of your test will be printed out. Plug this URL into your browser to view your page with all of its assets in full glory. This is especially useful when you’re trying to view your JS elements – save_and_open_page won’t allow you to do that.
In Conclusion
Rspec is a very powerful testing suite, complete with a lot of out of the box features. Leveraging its features are critical in creating efficient, readable tests. I’ve only scraped the tip of the useful Rspec strategies, so it would be fruitful to check out their online documentation for more exhaustive examples. Hopefully with these pointers your testing experience will be more satisfying and maybe even…enjoyable!