Monthly Archives: November 2021

Rails Rehashed: Chapter 8. Test Driven Development in a Rails Application

Test-Driven Development: Really, It's a Design Technique

Within the Rails there is a comprehensive testing structure embedded in the application that allows for continual testing over the course of building an application. The resulting test suite also 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 by quickly identifying and remediating defects – despite requiring extra code to be written.

Within Rails testing structure, principles of test-driven development (TDD) to write failing tests first, and then writes the application code to get the tests to pass can be easily applied.  At the same time, Rails development is not prescriptive to TDD and these principles can be applied when convenient without being dogmatic about it

When and how often test sometimes also depends on individual development style and preferences.  It is however helpful to understand why to test.  At a high level, automated testing has three main benefits:

  • Tests protect against regressions, where a functioning feature stops working for some reason
  • Tests allow code to be refactored (i.e., changing its form without changing its function) with greater confidence.
  • Tests act as a client for the application code, thereby helping determine its design and its interface with other parts of the system.

Although none of the above benefits require that tests be written first, there are many circumstances where test-driven development (TDD) is a valuable tool to have in your kit.  Deciding when and how to test depends in part on how comfortable you are writing tests.  Many developers find that, as they get better at writing tests, they are more inclined to write them first.  It also depends on how difficult the test is relative to the application code, how precisely the desired features are known, and how likely the feature is to break in the future.

In this context, it is helpful to have a set of guidelines on when we should test first; or test at all.  Below are some guidelines for when to consider writing test cases:

  • When a test is especially short or simple compared to the application code it tests, lean toward writing the test first.
  • When the desired behavior is not yet crystal clear, lean toward writing the application code first, then write a test to codify the result.
  • When security is a top priority, err on the side of writing tests of the security model first.
  • Whenever a bug is found, write a test to reproduce it and protect against regressions, then write the application code to fix it.
  • Lean against writing tests for code that is likely to change in the future.
  • Write tests before refactoring code, focusing on testing error-prone code that’s especially likely to break.

In practice, the guidelines above mean that we will usually write controller and model tests first and integration tests (which test functionality across models, views, and controllers) second.  When we’re writing application code that isn’t particularly brittle or error-prone, or is likely to change you may make a choice to skip testing altogether.

In Rails the main testing tools consist of controller tests, model tests, and integration tests.  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.

 Building a Test Case

As an example to starting to build out a simple test aces lets take for example a simple application with a static Home and Help pages.  In order to add an About page to our application, we will follow the above TDD guidelines to write the test first. We’ll then use the failing test to drive the writing of the application code.

If we had generated the static_pages controller using the generate command as below

$ rails generate controller StaticPages home help

A test file is also created in the following directory:

$ ls test/controllers

static_pages_controller_test.rb

test/controllers/static_pages_controller_test.rb

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

In this file, you can see that there are two tests, one for each controller action we included when we generated the controller.  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. The response :success is an abstract representation of the underlying HTTP status code (in this case, 200 OK). In other words the test scenario can be outlined as: “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:

$ rails db:migrate     # Necessary on some systems

$ rails test

2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

As you run these tests will notice a sqlite file created in your project directory:

/db/test.sqlite3-0

To prevent these generated files from being added to the repository, add a rule to the .gitignore file

.gitignore

.

.

.

# Ignore db test files.

db/test.*

In order to follow the concepts of Test Driven Development, let us write a failing test for an About page.

test/controllers/static_pages_controller_test.rb

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

Now if we run the test we will get the following output:

$ rails test

Error:

StaticPagesControllerTest#test_should_get_about:

NameError: undefined local variable or method `static_pages_about_url’ for #<StaticPagesControllerTest:0x00007f1d60085190>

    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>’

3 tests, 2 assertions, 0 failures, 1 errors, 0 skips

Now that we have a failing test, we can use the failing test error messages to guide us to a passing test, thereby implementing a working About page.

We can get started by examining the error message output by the failing 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.

config/routes.rb

Rails.application.routes.draw do

  get  ‘static_pages/home’

  get  ‘static_pages/help’

  get  ‘static_pages/about’

  root ‘application#index’

end

Running our test suite again, we now the error message has changed:

$ 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:

app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController

  def home

  end

  def help

  end

  def about

  end

end

As before the test is still failing 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.

Let us add an about view similar to home.html.erb located in the app/views/static_pages directory

$ touch app/views/static_pages/about.html.erb

Add the following content to the file:

app/views/static_pages/about.html.erb

<h1>About</h1>

<p>

  The <a href=”https://www.railstutorial.org/”><em>Ruby on Rails

  Tutorial</em></a>, part of the

  <a href=”https://www.learnenough.com/”>Learn Enough</a> family of

  tutorials, is a

  <a href=”https://www.railstutorial.org/book”>book</a&gt; and

  <a href=”https://screencasts.railstutorial.org/”>screencast series</a>

  to teach web development with

  <a href=”https://rubyonrails.org/”>Ruby on Rails</a>.

  This is the sample app for the tutorial.

</p>

At this point, running rails test should get us back to green:

$ rails test

3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

 Refactor Code

Now that we have got all the test cases passing we make changes to or simple application by refactoring code and explore how continual testing can help us fix issues as we make changes.

For the purpose of this exercise we will edit the Home, Help, and About pages to make page titles that change on each page. This will involve using the <title> tag in our page views. Most browsers display the contents of the title tag at the top of the browser window, and it is also important for search-engine optimization.

We will start a TDD cycle by first by adding simple tests for our page titles, then by adding titles to each of our three pages, and finally using a layout file to eliminate duplication. By the end of this section, all three of our static pages will have titles of the form “<page name> | Ruby on Rails Tutorial Sample App”, where the first part of the title will vary depending on the page.

The rails new command always creates a layout file by default.  Entries in this layout file are inherited by all views in the application.  For now lets move this file temporarily by changing its name:

$ mv app/views/layouts/application.html.erb app/views/layouts/application.html.erb.bak

The HTML structure below would typically add a title to a page.

<!DOCTYPE html>

<html>

  <head>

    <title>Greeting</title>

  </head>

  <body>

    <p>Hello, world!</p>

  </body>

</html>

We will not describe HTML tags in this chapter except for this short description.  Document type, or doctype, declaration at the top to tell browsers which version of HTML (in this case, HTML5); a head section, in this case with “Greeting” inside a title tag; and a body section, in this case with “Hello, world!” inside a p (paragraph) tag.

We will write simple tests for each of the titles fort the three pages by combining them with the tests we had written previously with an assert_select method, which lets us test for the presence of a particular HTML tag.

assert_select “title”, “Home | Ruby on Rails Tutorial Sample App”

In particular, the code above checks for the presence of a <title> tag containing the string “Home | Ruby on Rails Tutorial Sample App”.

Applying this idea to all three static pages gives the below test set:

test/controllers/static_pages_controller_test.rb

require ‘test_helper’

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  test “should get home” do

    get static_pages_home_url

    assert_response :success

    assert_select “title”, “Home | Ruby on Rails Tutorial Sample App”

  end

  test “should get help” do

    get static_pages_help_url

    assert_response :success

    assert_select “title”, “Help | Ruby on Rails Tutorial Sample App”

  end

  test “should get about” do

    get static_pages_about_url

    assert_response :success

    assert_select “title”, “About | Ruby on Rails Tutorial Sample App”

  end

end

Running the test file will give us the following error:

$ rails test

3 tests, 6 assertions, 3 failures, 0 errors, 0 skips

Now let us add the title HTML code “home” view:

app/views/static_pages/home.html.erb

<!DOCTYPE html>

<html>

  <head>

    <title>Home | Ruby on Rails Tutorial Sample App</title>

  </head>

  <body>

    <h1>Sample App</h1>

    <p>

      This is the home page for the

      <a href=”https://www.railstutorial.org/”>Ruby on Rails Tutorial</a>

      sample application.

    </p>

  </body>

</html>

Let us make the same changes for “help” and “about” views and run the test suite :

$ rails test

3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

Now that we have the functionality built and passing the test suite we can start refactoring code to make our application more efficient .

The first thing you will notice is that in the Static Pages controller test we have repeated base title, “Ruby on Rails Tutorial Sample App”.  We can run a special function “setup” to get rid of this repetition.

              test/controllers/static_pages_controller_test.rb

require ‘test_helper’

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  def setup

    @base_title = “Ruby on Rails Tutorial Sample App”

  end

We can then replace the text with “| #{@base_title}” in each test case and run the test cases to make sure they still hold integrity.

The next thing you can note is tha each of the “home”, “help” and ‘about” have quite a bit of duplicated code: the page titles are almost the same; the entire HTML skeleton structure is repeated on each page.

This repeated code is a violation of the important Rails “Don’t Repeat Yourself” (DRY) principle.

Let us refractor the code to eliminate the duplicates and re-run the tests to verify that the titles are still correct.  We will also start leveraging on embedded Ruby (ERb) in the views to achieve this refactoring. Since the “home”, “help”, and “about” views have titles have a variable component, we will use the “provide” function to set a different title in each view.

app/views/static_pages/home.html.erb

<% provide(:title, “Home”) %>

<!DOCTYPE html>

<html>

  <head>

    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>

  </head>

  .

</html>

Let us look in more detail of what <% provide(:title, “Home”) %> is doing:

<% … %> indicates for Rails to call the provide function and associate the string “Home” with the label :title.  Then, <%= yield(:title) %> indicated rails to call the yield function to apply the associated “Home” string to the label :title.

We can do the same to all the three views and verify it works.

Now that we’ve replaced the variable part of the page titles with ERb, each of our pages looks something like this:

<% provide(:title, “Page Title”) %>

<!DOCTYPE html>

<html>

  <head>

    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>

  </head>

  <body>

    Contents

  </body>

</html>

In other words, all the pages are identical in structure, including the contents of the title tag, with the sole exception of the material inside the body tag.

In order to factor out this common structure, Rails comes with a special layout file called application.html.erb, which we renamed in the beginning of this section.

$ mv layout_file app/views/layouts/application.html.erb

To get the layout to work, we have to replace the default title with the embedded Ruby from the examples above:

<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>

Resulting in the following file:

app/views/layouts/application.html.erb

<!DOCTYPE html>

<html>

  <head>

    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>

    <meta charset=”utf-8″>

    <%= csrf_meta_tags %>

    <%= csp_meta_tag %>

    <%= stylesheet_link_tag ‘application’, media: ‘all’,

                                           ‘data-turbolinks-track’: ‘reload’ %>

    <%= javascript_pack_tag ‘application’, ‘data-turbolinks-track’: ‘reload’ %>

  </head>

  <body>

    <%= yield %>

  </body>

</html>

Note here the special line

<%= yield %>

This code is responsible for inserting the contents of each page into the layout.  At this time, it is not important to know exactly how this works; what matters is that using this layout ensures that all views converts the contents and then inserts it in place of <%= yield %>.

Finally, it is worth noting that the default Rails layout includes several additional lines:

<%= csrf_meta_tags %>

<%= csp_meta_tag %>

<%= stylesheet_link_tag … %>

<%= javascript_pack_tag “application”, … %>

This code arranges to include the application stylesheet and JavaScript, which are part of the asset pipeline, together with the Rails method csp_meta_tag, which implements Content Security Policy (CSP) to mitigate cross-site scripting (XSS) attacks, and csrf_meta_tags, which mitigates cross-site request forgery (CSRF) attacks.

Even though the tests are passing, there’s one detail left to deal with in the views.  The duplicated HTML codes needs to be removed.  Once this is removed the views will look mucs simpler as below:

app/views/static_pages/home.html.erb

<% provide(:title, “Home”) %>

<h1><%= yield(:title) %></h1>

  <p>

    This is the home page for the

    <a href=”https://www.railstutorial.org/”>Ruby on Rails Tutorial</a>

    sample application!

  </p>

app/views/static_pages/help.html.erb

<% provide(:title, “Help”) %>

<h1><%= yield(:title) %></h1>

<p>

  Get help on the Ruby on Rails Tutorial at the

  <a href=”https://www.railstutorial.org/help”>Rails Tutorial help page</a>. To get help on this sample app, see the

  <a href=”https://www.railstutorial.org/book”><em>Ruby on Rails Tutorial</em> book</a>.

</p>

app/views/static_pages/about.html.erb

<% provide(:title, “About”) %>

<h1><%= yield(:title) %></h1>

<p>

  The

  <a href=”https://www.railstutorial.org/”><em>Ruby on Rails Tutorial</em></a>, part of the

  <a href=”https://www.learnenough.com/”>Learn Enough</a> family of tutorials,is a

  <a href=”https://www.railstutorial.org/book”>book</a&gt; and

  <a href=”https://screencasts.railstutorial.org/”>screencast series</a> to teach web development with

  <a href=”https://rubyonrails.org/”>Ruby on Rails</a>.

  This is the sample app for the tutorial.

</p>

With the above edits made to the application.html.erb file and to the three views, let us run the tes suite again to ensure we have maintained the functional integrity and not introduced any defects.

It would be good to commit these changes to Git:

 $ git status    # It’s a good habit to check the status before adding

$ git add -A

$ git commit -m “Finished refactering views”

$ git push

To reinforce the concepts let us now add a “contact” page using the principles applied above.

  • Write a test case for “contact” page in static_pages_controller_tests.rb file
  • Run the test suite after each one of the below steps to ensure the test suite finally passes passes without errors
  • Add the route to reoutes.rb
  • Define contact action in static_pages_controller.rb
  • Create a contact.html.erb view file

Now we can make one other tweak.  Instead of the application root directing to index we can repoint it to home by making the following changes in the reoutes.rb file

root ‘application#index’

to

root ‘static_pages#home’

Now that you have redirected the the root to “home” it is a good idea to add a test case to the test suite in static_pages_controller.rb:

  test “should get root” do

    get static_pages_home_url

    assert_response :success

  end

before finishing up let us take a minute to commit the changes on our topic branch and merge them into the main branch.

$ git add -A

$ git commit -m “Finish static pages”

Then merge the changes back into the main branch using the same technique as in Section 1.3.4:16

$ git checkout main

$ git merge static-pages

$ git push

$ git push heroku

 Automated Tests with Guard

Guard is a powerful application that can monitors changes in the filesystem so that when we …_controller_test.rb file is changed a a new test added only the new tests get run.  Additionally when a controller or a view file is modified, the test suite in …controller_test.rb will run automatically.

The Gemfile build when we create a new Rails application already included the guard gem.  It just needs to be initialized.

$ bundle _2.2.17_ exec guard init

We then edit the resulting Guardfile so that Guard will run the right tests when the integration tests and views are updated.  Below is a suggested version of the Guardfile listed for your reference.

Reference Guardfile at railstutorial.org/guardfile

require ‘active_support/core_ext/string’

# Defines the matching rules for Guard.

guard :minitest, spring: “bin/rails test”, all_on_start: false do

  watch(%r{^test/(.*)/?(.*)_test\.rb$})

  watch(‘test/test_helper.rb’) { ‘test’ }

  watch(‘config/routes.rb’) { interface_tests }

  watch(%r{app/views/layouts/*}) { interface_tests }

  watch(%r{^app/models/(.*?)\.rb$}) do |matches|

    [“test/models/#{matches[1]}_test.rb”,

     “test/integration/microposts_interface_test.rb”]

  end

  watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches|

    “test/models/#{matches[1].singularize}_test.rb”

  end

  watch(%r{^app/mailers/(.*?)\.rb$}) do |matches|

    “test/mailers/#{matches[1]}_test.rb”

  end

  watch(%r{^app/views/(.*)_mailer/.*$}) do |matches|

    “test/mailers/#{matches[1]}_mailer_test.rb”

  end

  watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|

    resource_tests(matches[1])

  end

  watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|

    [“test/controllers/#{matches[1]}_controller_test.rb”] +

    integration_tests(matches[1])

  end

  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|

    integration_tests(matches[1])

  end

  watch(‘app/views/layouts/application.html.erb’) do

    ‘test/integration/site_layout_test.rb’

  end

  watch(‘app/helpers/sessions_helper.rb’) do

    integration_tests << ‘test/helpers/sessions_helper_test.rb’

  end

  watch(‘app/controllers/sessions_controller.rb’) do

    [‘test/controllers/sessions_controller_test.rb’,

     ‘test/integration/users_login_test.rb’]

  end

  watch(‘app/controllers/account_activations_controller.rb’) do

    ‘test/integration/users_signup_test.rb’

  end

  watch(%r{app/views/users/*}) do

    resource_tests(‘users’) +

    [‘test/integration/microposts_interface_test.rb’]

  end

end

# Returns the integration tests corresponding to the given resource.

def integration_tests(resource = :all)

  if resource == :all

    Dir[“test/integration/*”]

  else

    Dir[“test/integration/#{resource}_*.rb”]

  end

end

# Returns all tests that hit the interface.

def interface_tests

  integration_tests << “test/controllers”

end

# Returns the controller tests corresponding to the given resource.

def controller_test(resource)

  “test/controllers/#{resource}_controller_test.rb”

end

# Returns all tests for the given resource.

def resource_tests(resource)

  integration_tests(resource) << controller_test(resource)

end

On the cloud IDE, you have to run one additional step to allow Guard to monitor all the files in the project:

$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf

$ sudo sysctl -p

Once Guard is configured, you should open a new terminal (as with the Rails server in Section 1.2.2) and run it at the command line as follows (Figure 3.11):

$ bundle _2.2.17_ exec guard

To run all the tests, simply hit return at the guard> prompt.

To exit Guard, press Ctrl-D.

To add additional matchers to Guard refer to the Guard README, and the Guard wiki.

If the test suite fails without apparent cause, try exiting Guard, stopping Spring

$ bin/spring stop    # Try this if the tests mysteriously start failing.

$ bundle _2.2.17_ exec guard

Test Guard is working by making a small change to one of the views and saving the file.

Before finally finishing, you should add your changes and make a commit:

$ git add -A

$ git commit -m “Complete advanced testing setup”

$ git push

$ git push heruko

You now know how to:

  • Create a Rails development environment
  • Create a new application structure and deploy all the dependency bundles
  • Scaffold and edit the controllers, models, and views for specific application functionality
  • Use REST calls to create routes to manoeuvre through the Rails application
  • Develop test cases and leverage on a TDD structure within Rails
  • Version control locally and push out to a remote repository
  • Deploy you application so that it is accessible to external consumers

You are now a Ruby on Rails Novice! Start developing with Rails!

Go Back to: Chapter 7. Build a Rails Application in Less Than a Day

Go back to: Rails Rehashed