3.3 Getting Started with Testing
Having created and filled in the Home and Help pages for our sample app (Section 3.2.2), now we’re going to add an About page as well. When making a change of this nature, it’s a good practice to write an automated test to verify that the feature is implemented correctly. Developed over the course of building an application, the resulting test suite serves as a safety net and as executable documentation of the application source code. When done right, writing tests also allows us to develop faster despite requiring extra code, because we’ll end up wasting less time trying to track down bugs. This is true only once we get good at writing tests, though, which is one reason it’s important to start practicing as early as possible.
Although virtually all Rails developers agree that testing is a good idea, there is a diversity of opinion on the details. There is an especially lively debate over the use of test-driven development (TDD),7 a testing technique in which the programmer writes failing tests first, and then writes the application code to get the tests to pass. The Ruby on Rails™ Tutorial takes a lightweight, intuitive approach to testing, employing TDD when convenient without being dogmatic about it (Box 3.3).
Our main testing tools will be controller tests (starting in this section), model tests (starting in Chapter 6), and integration tests (starting in Chapter 7). Integration tests are especially powerful, as they allow us to simulate the actions of a user interacting with our application using a web browser. Integration tests will eventually be our primary testing technique, but controller tests give us an easier place to start.
3.3.1 Our First Test
Now it’s time to add an About page to our application. As we’ll see, the test is short and simple, so we’ll follow the guidelines from Box 3.3 and write the test first. We’ll then use the failing test to drive the writing of the application code.
Getting started with testing can be challenging, requiring extensive knowledge of both Rails and Ruby. At this early stage, writing tests might thus seem hopelessly intimidating. Luckily, Rails has already done the hardest part for us, because rails generate controller (Listing 3.6) automatically generated a test file to get us started:
$ ls test/controllers/ static_pages_controller_test.rb
Let’s take a look at it (Listing 3.13).
Listing 3.13: The default tests for the StaticPages controller. GREEN
require 'test_helper' class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url assert_response :success end test "should get help" do get static_pages_help_url assert_response :success end end
It’s not important at this point to understand the syntax in Listing 3.13 in detail, but we can see that there are two tests, one for each controller action we included on the command line in Listing 3.6. Each test simply gets a URL and verifies (via an assertion) that the result is a success. Here the use of get indicates that our tests expect the Home and Help pages to be ordinary web pages, accessed using a GET request (Box 3.2). The response :success is an abstract representation of the underlying HTTP status code (in this case, 200 OK). In other words, a test like
test "should get home" do get static_pages_home_url assert_response :success end
says “Let’s test the Home page by issuing a GET request to the Static Pages home URL and then making sure we receive a ‘success’ status code in response.”
To begin our testing cycle, we need to run our test suite to verify that the tests currently pass. We can do this with the rails command as follows:
Listing 3.14: GREEN
$ rails test 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
As required, initially our test suite is passing (GREEN). (You won’t actually see the color green unless you add the minitest reporters from the optional Section 3.6.1, but the terminology is common even when literal colors aren’t involved.) On some systems, the tests can take a relatively long time to start, which is due to two factors: (1) starting the Spring server to preload parts of the Rails environment, which only happens the first time; and (2) overhead associated with Ruby startup time. (The second factor is ameliorated when using Guard as suggested in Section 3.6.2.)
As noted in Box 3.3, test-driven development involves writing a failing test first, writing the application code needed to get it to pass, and then refactoring the code if necessary. Because many testing tools represent failing tests with the color red and passing tests with the color green, this sequence is sometimes known as the “Red, Green, Refactor” cycle. In this section, we’ll complete the first step in this cycle, getting to RED by writing a failing test. Then we’ll get to GREEN in Section 3.3.3, and refactor in Section 184.108.40.206
Our first step is to write a failing test for the About page. Following the models from Listing 3.13, you can probably guess the right test, which is shown in Listing 3.15.
Listing 3.15: A test for the About page. RED
require 'test_helper' class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url assert_response :success end test "should get help" do get static_pages_help_url assert_response :success end test "should get about" do get static_pages_about_url assert_response :success end end
We see from the highlighted lines in Listing 3.15 that the test for the About page is the same as the Home and Help tests with the word “about” in place of “home” or “help”.
As required, the test initially fails:
Listing 3.16: RED
$ rails test 3 tests, 2 assertions, 0 failures, 1 errors, 0 skips
Now that we have a failing test (RED), we’ll use the failing test’s error messages to guide us to a passing test (GREEN), thereby implementing a working About page.
We can get started by examining the error message output by the failing test:
Listing 3.17: RED
$ rails test NameError: undefined local variable or method `static_pages_about_url'
The error message here says that the Rails code for the About page URL is undefined, which is a hint that we need to add a line to the routes file. We can accomplish this by following the pattern in Listing 3.7, as shown in Listing 3.18.
Listing 3.18: Adding the about route. RED
Rails.application.routes.draw do get 'static_pages/home' get 'static_pages/help' get 'static_pages/about' root 'application#hello' end
The highlighted line in Listing 3.18 tells Rails to route a GET request for the URL /static_pages/about to the about action in the Static Pages controller. This automatically creates a helper called
Running our test suite again, we see that it is still RED, but now the error message has changed:
Listing 3.19: RED
$ rails test AbstractController::ActionNotFound: The action 'about' could not be found for StaticPagesController
The error message now indicates a missing about action in the Static Pages controller, which we can add by following the model provided by home and help in Listing 3.8, as shown in Listing 3.20.
Listing 3.20: The Static Pages controller with added about action. RED
class StaticPagesController < ApplicationController def home end def help end def about end end
As before, our test suite is still RED, but the error message has changed again:
$ rails test ActionController::UnknownFormat: StaticPagesController#about is missing a template for this request format and variant.
This indicates a missing template, which in the context of Rails is essentially the same thing as a view. As described in Section 3.2.1, an action called home is associated with a view called home.html.erb located in the app/views/static_pages directory, which means that we need to create a new file called about.html.erb in the same directory.
The way to create a file varies by system setup, but most text editors will let you control-click inside the directory where you want to create the file to bring up a menu with a “New File” menu item. Alternately, you can use the File menu to create a new file and then pick the proper directory when saving it. Finally, you can use my favorite trick by applying the Unix touch command as follows:
$ touch app/views/static_pages/about.html.erb
As mentioned in Learn Enough Command Line to Be Dangerous, touch is designed to update the modification timestamp of a file or directory without otherwise affecting it, but as a side-effect it creates a new (blank) file if one doesn’t already exist. (If using the cloud IDE, you may have to refresh the file tree as described in Section 1.3.1. This is a good example of technical sophistication [Box 1.1].)9
Once you’ve created the about.html.erb file in the right directory, you should fill it with the contents shown in Listing 3.21.
Listing 3.21: Code for the About page. GREEN
<h1>About</h1> <p> The <a href="http://www.railstutorial.org/"><em>Ruby on Rails Tutorial</em></a> is a <a href="http://www.railstutorial.org/book">book</a> and <a href="http://screencasts.railstutorial.org/">screencast series</a> to teach web development with <a href="http://rubyonrails.org/">Ruby on Rails</a>. This is the sample application for the tutorial. </p>
At this point, running rails test should get us back to GREEN:
Listing 3.22: GREEN
$ rails test 3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
Of course, it’s never a bad idea to take a look at the page in a browser to make sure our tests aren’t completely crazy (Figure 3.5).
Figure 3.5: The new About page (/static_pages/about).
Now that we’ve gotten to GREEN, we are free to refactor our code with confidence. When developing an application, often code will start to “smell”, meaning that it gets ugly, bloated, or filled with repetition. The computer doesn’t care what the code looks like, of course, but humans do, so it is important to keep the code base clean by refactoring frequently. Although our sample app is a little too small to refactor right now, code smell seeps in at every crack, and we’ll get started refactoring in Section 3.4.3.