Test-Driven Development for Rails Applications
Red, Green, Refactor
One of the best ways to ensure your Rails application is set up for success is to build tests from the very beginning of development. Ruby on Rails is known to be highly opinionated and as such, there are specific preferences about how to effectively set up tests. The recommended workflow is referred to as ‘Red, Green, Refactor’. Following this guide, we start out by writing our tests based on the behavior we’d like to see, writing the code needed for them to pass, and then tidy up our methods and implementation. We’ll dive deeper into each of these steps but first, let’s get familiar with the tools we need to run these tests and how to set them up in our environment.
Common Rails Testing Tools
RSpec is made up of a few frameworks and libraries that allow us to run tests, create fake objects to test functionality and interactions, readable APIs, and a special library for rails support called rspec-rails.
For acceptance tests, Capybara allows us to simulate real user interactions. It comes with built-in Selenium support and can be used with Cucumber as well.
Setting up these libraries and dependencies is fairly simple. If you’re creating a new application, append the -T flag upon creation to skip the generation of a test unit directory. Then we’ll want to add the rspec-rails gem to the development test group and the capybara to the test group in the Gemfile. Of course, we’ll need to run bundle install at this point to update our gems. Finally, run rails g rspec:install from the command line to create a .rspec file and a spec directory that encompasses our rails_helper.rb and spec_helper.rb files.
The Spec Directory
As you can imagine, all of our test files will live in the spec directory. By default, when we run the rake command, all files located in the spec directory will be run. The naming convention for all test suites is test_description_spec.rb. Including _spec.rb at the end of the file name, tells Ruby that this is a test.
In the .rspec file, you might find some stand-alone flags like — color, — format, etc. These flags are used to specify how you want the feedback from tests to look. For example, you can use the — format HTML flag to see results in HTML which can be a great tool for continuous integration.
Rails helper was introduced to support rails-specific testing. We still need spec helper to perform tests on any code that lives within the lib directory, but we only need to require rails_helper.rb at the top of our spec files when testing applications. This is a super important step that is easy to forget, so make this the first thing that you do when you create a new spec file! You can take a shortcut by requiring rails_helper.rb at the top of the .rspec file instead, but some would argue that this increases test run time.
Anytime we need to define helper methods for our specs, they will live in their own file within a spec/support folder. This can be useful when refactoring to tidy up any repetitive code.
We can break down the tests we are going to write into three parts: calling the method being tested (exercise), creating and configuring objects needed (set up), and checking the results (verification).
The rule of thumb here is to only test one branch or method per spec. That way you can really pinpoint the area of concern and more easily find the root cause of failures. Effective tests mimic intended real-life interactions with our application as closely as possible.
RSpec has some built-in methods that we can use in our test suites. The most used are describe and it. Describe is used to group examples and/or properties being examined. While the ‘it’ keyword refers to one specific instance. Here’s an example of how ‘describe’ and ‘it’ methods are used together.
describe Todo, '#completed?' do it 'returns true if completed_at is set' do todo = Todo.new(completed_at: Time.current) expect(todo).to be_completed end
it 'returns false if completed_at is nil' do todo = Todo.new(completed_at: nil) expect(todo).to_not be_completed endend
While you may not understand all of the code above, notice how the ‘it’ implementation blocks are nested within the ‘describe’ method. We are describing two instances here that test the completion of a todo object.
Expect is another built-in RSpec method that you will often use. We can pass expect an argument to be tested and then explain in almost plain English what our assertions are thanks to RSpec’s matchers.
Our acceptance tests will follow a similar syntax except we’ll use the feature and scenario keywords to replace the ‘describe’ and ‘it’ keywords like so…
feature 'user visits homepage' do scenario 'successfully' do #you have access to rails route helpers here visit root_path expect(page).to have_css 'h1', text: 'Todos' endend
Here we are explaining that once a user visits the root path, we expect an h1 with the text ‘Todos’ to be displayed. Pretty straightforward, right?
Red to Green
The red, green, refactor process can be more like red, green, red, green, red, green…. refactor in practice. After writing a spec, running it even before writing any code is written can guide you in the right direction on what needs to be implemented.
Start by writing the smallest possible addition of code like defining a method without any code inside, then run the test again. The spec will see that you have now defined the method to be tested and you can let the error messages guide you from there until the expected functionality is achieved.
Once you get that test passing, run rake to test the whole suite and make sure all other tests are passing. This is an important step that ensures that your newly tested method doesn’t cause anything else to break. If all tests are passing, we can finally move on to refactoring.
In the refactoring step, we get to look at all of our source code and ask ourselves what we can improve. You may want to refactor for readability or to DRY out your code.
As mentioned previously, we can define helper methods for our spec files in the spec/support folder to be called in test suites. Many times the actions that we are testing, require a similar setup and helper methods can be used to save time and space. We can clean up our spec files by extracting repetitive lines of code and adding them to a helper method.
The last configuration to be aware of is the database_cleaner.rb file. This file can be created and stored in the spec/support folder. It houses methods needed to reset our database before and after running unit and/or acceptance tests. There are many different options here, but the most basic methods you’ll need are the following:
RSpec.configure do |config|#any thing in the db at begin of test suite is cleared config.before(:suite) do DatabaseCleaner.clean_with :truncation end#before each test start cleaner config.before(:each) do DatabaseCleaner.start end#after each test clear database config.after(:each) do DatabaseCleaner.clean end
Keep It Going
Although we have only scratched the surface in this beginner’s guide, I hope that you continue this journey. Test-driven development establishes a greater sense of confidence in our application’s abilities and gives us the opportunity to build models, controllers, and views with the end goal defined from the start.
Keep learning by checking out the documentation and tutorials listed below. You’ll be on your way to mastering Rails tests in no time.