Full stack integration testing with Rails3: browser-oriented, JavaScript, continuous integration

This post is a complete summary of all my efforts to have a complete and decent solution to test Rails applications.

As it is a long post, here you have the table of contents:

  1. Introduction
  2. Prerequisite for this tutorial
    1. Ruby and Rails environment
    2. OS
  3. First: helpers to write readable tests: RSpec and Cucumber
  4. Test your website using a real browser engine: Capybara and Webkit
  5. First step in continuous integration: having a running instance forking the Rails environment: Spork
  6. Second step in continuous integration: listening to source changes with Guard
  7. Summary

Introduction

Rails comes with a natural support for ruby’s Test::Unit, that comes very handy and lightweight. I still think this is a great lib to write your unit tests. However when it comes to integration testing, you will likely end up with writing big helper methods that will mimic a browser’s behaviour, and its corresponding assertions.

So, I wanted to have a great, efficient, portable and easily maintainable solution to test the complete integration of my Rails applications.
Complete integration testing means that I have complex work-flows to test, using heavy JavaScript (jQuery, Mootools…), dynamic DOM manipulations, involving several HTTP requests.
I also wanted to have a little plus in using this solution into a continuous integration process (a stand-alone instance monitors my source repository, and automatically executes tests upon file changes, producing reports).

Well… I found it and I am pretty happy with it so far.

The winning stack is:

I will explain the use of each one of them, for you to be able to pick exactly what you just need.

The goal of this tutorial is not to teach all the tricks of those libraries, but to show you their usage, how they can be useful to you, and how to integrate them together along with simple examples. If you want to get more in depth with each library/framework, please visit their associated link.

Prerequisite for this tutorial

Ruby and Rails environment

This tutorial assumes that you have a working Rails (I tested with 3.2.6, but I think >=3.2.1 is enough) application. Working at least means that it does not fail when running it using rails server and accessing it through your local browser at http://localhost:3000

The Ruby versions I used for this tutorial are 1.9.2 and 1.9.3. However unless some gems require otherwise, other Ruby versions should be accepted also.

I am also using Bundle in this tutorial. This is not mandatory, but I think this is a great way to express gem dependencies, and so I will illustrate this tutorial with Gemfile modifications, adding gems to it. If you don’t use bundle, just look at the gems I am adding to the file called Gemfile, and install them manually using gem install gem_name.

OS

I tested this tutorial on several OS, with the following results:

  • Windows 7 64bits system (native): Guard does not work on native Windows. However the tutorial can still be applied up to Spork usage.
  • Windows 7 64bits system (Cygwin): I could not make Cucumber work with Spork (however my Cygwin install might have been old and corrupted, so you can be luckier than me).
  • Ubuntu 12.04: Everything works fine.

First: helpers to write readable tests: RSpec and Cucumber

Tests should be written for developers to see them fail.
Then, those developers should understand quickly the implications of the failed test. That means a very important thing for a test case is that the developer should understand what is tested, to help him narrow his bug’s location in the code.
Therefore, writing readable tests is a must.

I found out 2 very useful libs for this purpose:

  • RSpec: it adds a lot of graceful vocabulary to your tests, and organize them clearly. In this tutorial, I mainly use RSpec for the extra helpers it provides.
  • Cucumber: it makes you use your own language and DSL to describe your tests. With this framework, even a newbie will be able to understand your tests. In this tutorial, I use cucumber to write the test cases themselves, and also run them.

Installing these libs in your Rails application is really easy:

  1. Add the gems dependencies in your application’s Gemfile, for group :test only:

    group :test do
    
      # RSpec
      gem 'rspec'
      gem 'rspec-rails'
    
      # Cucumber
      gem 'cucumber-rails', :require => false
      gem 'database_cleaner'
    
    end
    
  2. Run the bundle command to install the gems:

    > bundle
    
    Fetching gem metadata from https://rubygems.org/.......
    ...
    Installing gherkin (2.11.1)
    Installing cucumber (1.2.1)
    Installing cucumber-rails (1.3.0)
    Installing database_cleaner (0.8.0)
    Installing rspec-core (2.11.0)
    Installing rspec-expectations (2.11.1)
    Installing rspec-mocks (2.11.1)
    Installing rspec (2.11.0)
    Installing rspec-rails (2.11.0)
    ...
    Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
    
  3. If this is the first time you run tests on your application, don’t forget to first prepare your test database:

    > rake db:migrate
    > rake db:test:load
    
  4. Those frameworks need little configuration files. They can be generated with the following commands:

    > rails generate rspec:install
          create  .rspec
          create  spec
          create  spec/spec_helper.rb
    

    and

    > rails generate cucumber:install
          create  config/cucumber.yml
          create  script/cucumber
           chmod  script/cucumber
          create  features/step_definitions
          create  features/support
          create  features/support/env.rb
           exist  lib/tasks
          create  lib/tasks/cucumber.rake
            gsub  config/database.yml
            gsub  config/database.yml
           force  config/database.yml
    
  5. Finally you can write a nice test. This tutorial will make use of Cucumber to write and run tests.
    Therefore I define my test in my own language in a features file:

    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code
        When I visit the page "/"
        Then the response code should be "200"
    
  6. And I can run my test by invoking cucumber:

    > cucumber
    
    Using the default profile...
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features\test_cases\simple.feature:3
        When I visit the page "/"                               # features\test_cases\simple.feature:4
          Undefined step: "I visit the page "/"" (Cucumber::Undefined)
          features\test_cases\simple.feature:4:in `When I visit the page "/"'
        Then the response code should be "200"                  # features\test_cases\simple.feature:5
          Undefined step: "the response code should be "200"" (Cucumber::Undefined)
          features\test_cases\simple.feature:5:in `Then the response code should be "200"'
    
    1 scenario (1 undefined)
    2 steps (2 undefined)
    0m0.020s
    
    You can implement step definitions for undefined steps with these snippets:
    
    When /^I visit the page "(.*?)"$/ do |arg1|
      pending # express the regexp above with the code you wish you had
    end
    
    Then /^the response code should be "(.*?)"$/ do |arg1|
      pending # express the regexp above with the code you wish you had
    end
    

    You can see a colored output (on Windows, you will need to use Ansicon to see the colors), much more sexier than usual Test::Unit output.

    Currently our test is failing, as Cucumber does not know yet what to do with our language. As you can see at the end of the output, it suggests a nice code snippet with a regular expression to implement the missing definition.
    This is a real time saver: all you have to do is copy/paste this code snippet to your definition file, and implement it.

  7. So next step is to implement the missing definition, done in a definitions file:

    When /^I visit the page "(.*?)"$/ do |iPagePath|
      visit iPagePath
    end
    
    Then /^the response code should be "(.*?)"$/ do |iResponseCode|
      page.status_code.should == iResponseCode.to_i
    end
    

    In this example, some RSpec and Capybara syntax has been used. For a complete description of the associated DLL, see RSpec documentation and Capybara documentation.

  8. Now you can re-run your cucumber test suite and admire the result:

    > cucumber
    Using the default profile...
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features\test_cases\simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    1 scenario (1 passed)
    2 steps (2 passed)
    0m2.551s
    

With these you can already write a great test suite, very easy to maintain with reusable and understandable sentences in your tests. Developers working after you on your project will thank you!

Test your website using a real browser engine: Capybara and Webkit

The best way for your tests to be as close as what a user on his browser might do, is to have a real browser engine for your tests, along with a browser-oriented API.
This is the exact purpose of Capybara.

Capybara gives you a browser-oriented API (like visit '/path/to/page?param=1', fill_in 'Login', :with => 'user@example.com', click_link 'Sign in'…)

It comes with a variety of browser engines or emulations, and one of the best is simply the Webkit engine, used in modern browsers such as Safari or Chrome.
Using Capybara/webkit can be a bit cumbersome on some systems as it requires the presence of Qt installed. If this is too much trouble, you can use default capybara drivers (selenium is a good choice) for your tests to pass.

For the sake of this tutorial, we will use the Webkit engine on capybara.

  1. First step is to add the gems to your Gemfile (still in the :test group):

      # Capybara-webkit
      gem 'capybara-webkit'
    

    If you decided to use capybara with default drivers, the gem name is capybara.

  2. And once again, install it:

    > bundle
    ...
    Using capybara-webkit (0.12.1)
    ...
    Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
    
  3. You can then modify capybara’s configuration by editing the cucumber’s environment file. Most importantly you need to select the desired driver (Webkit in our example):

    Capybara.javascript_driver = :webkit
    

    If you want the selenium driver, the name is :selenium.

  4. Now we will add a new test case involving some JavaScript, and parse it using Capybara’s API:

    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript
        When I visit the page "/"
          And I add a link "Click me to replace content" replacing "body" content with "New content"
          And I click "Click me to replace content"
        Then "body" content should be "New content"
    

    Please note the use of the @javascript tag, mandatory for each test requiring the JavaScript engine to run.

  5. Run cucumber a first time to check for missing definitions

    > cucumber
    Using the default profile...
    Feature: JavaScript testing
    
      Scenario: Arriving on home page and executing JavaScript                                                   # features\test_cases\javascript.feature:3
        When I visit the page "/"                                                                                # features/step_definitions/main_steps.rb:1
        And I add a link "Click me to replace content" replacing "body" content with "Content has been replaced" # features\test_cases\javascript.feature:5
          Undefined step: "I add a link "Click me to replace content" replacing "body" content with "Content has been replaced"" (Cucumber::Undefined)
          features\test_cases\javascript.feature:5:in `And I add a link "Click me to replace content" replacing "body" content with "Content has been replaced"'
        And I click "Click me to replace content"                                                                # features\test_cases\javascript.feature:6
          Undefined step: "I click "Click me to replace content"" (Cucumber::Undefined)
          features\test_cases\javascript.feature:6:in `And I click "Click me to replace content"'
        Then "body" content should be "Content has been replaced"                                                # features\test_cases\javascript.feature:7
          Undefined step: ""body" content should be "Content has been replaced"" (Cucumber::Undefined)
          features\test_cases\javascript.feature:7:in `Then "body" content should be "Content has been replaced"'
    
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features\test_cases\simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    2 scenarios (1 undefined, 1 passed)
    6 steps (3 undefined, 3 passed)
    0m3.581s
    
    You can implement step definitions for undefined steps with these snippets:
    
    When /^I add a link "(.*?)" replacing "(.*?)" content with "(.*?)"$/ do |arg1, arg2, arg3|
      pending # express the regexp above with the code you wish you had
    end
    
    When /^I click "(.*?)"$/ do |arg1|
      pending # express the regexp above with the code you wish you had
    end
    
    Then /^"(.*?)" content should be "(.*?)"$/ do |arg1, arg2|
      pending # express the regexp above with the code you wish you had
    end
    
  6. And now implement those definitions:

    ...
    When /^I add a link "(.*?)" replacing "(.*?)" content with "(.*?)"$/ do |iLinkName, iElementSelector, iNewContent|
      page.execute_script("
        var lLinkElement = document.createElement('a');
        lLinkElement.setAttribute('onclick','$(\\'#{iElementSelector}\\')[0].innerHTML = \\'#{iNewContent}\\'');
        lLinkElement.setAttribute('href', '#');
        lLinkElement.innerHTML = '#{iLinkName}';
        $('body')[0].appendChild(lLinkElement);
      ")
    end
    
    When /^I click "(.*?)"$/ do |iLinkName|
      click_on iLinkName
    end
    
    Then /^"(.*?)" content should be "(.*?)"$/ do |iElementSelector, iContent|
      find(iElementSelector).should have_content(iContent)
    end
    
  7. Check everything works fine:

    > cucumber
    Using the default profile...
    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript                                     # features\test_cases\javascript.feature:4
        When I visit the page "/"                                                                  # features/step_definitions/main_steps.rb:1
        And I add a link "Click me to replace content" replacing "body" content with "New content" # features/step_definitions/main_steps.rb:9
        And I click "Click me to replace content"                                                  # features/step_definitions/main_steps.rb:19
        Then "body" content should be "New content"                                                # features/step_definitions/main_steps.rb:23
    
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features\test_cases\simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    2 scenarios (2 passed)
    6 steps (6 passed)
    0m9.140s
    

Now you have all the power to write complete integration tests, readable, maintainable and also very complex thanks to Capybara’s browser-oriented API.

First step in continuous integration: having a running instance forking the Rails environment: Spork

Up to now, each time you want to run your tests suite, your whole Rails application is booted and your test suite is run.
This booting process can be very time consuming, and if you want to run your tests often (and in fact you should), then you want to get rid of this booting time.

Here is when Spork shows up.
Spork is a test server that keeps your Rails environment in memory and forks it each time a test suite requires a fresh new instance of your Rails application. Therefore instead of having to boot your application before a test suite run, you just have a simple process fork: much faster!

  1. As usual we start by adding the Spork gem dependency in the Gemfile (still the :test group):

      # Spork
      gem 'spork-rails'
    
  2. And we install again:

    > bundle
    ...
    Using spork (1.0.0rc3)
    Using spork-rails (3.2.0)
    ...
    Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
    
  3. Now Spork needs some generated configuration:

    > spork cucumber --bootstrap
    Using Cucumber, Rails
    Bootstrapping /path/to/rails_app/features/support/env.rb.
    Done. Edit /path/to/rails_app/features/support/env.rb now with your favorite text editor and follow the instructions.
    
  4. The environment file has been modified. Now you really need to read and edit it, as you have to dispatch its content into 2 generated Spork callbacks. One will be called during Spork startup (prefork), the other by each instance that will be forked (each_run).
    For our example, we just have to move the complete capybara configuration in the booting callback:

    require 'rubygems'
    require 'spork'
    #uncomment the following line to use spork with the debugger
    #require 'spork/ext/ruby-debug'
    
    Spork.prefork do
      # Loading more in this block will cause your tests to run faster. However,
      # if you change any configuration or code from libraries loaded here, you'll
      # need to restart spork for it take effect.
    
      require 'cucumber/rails'
      Capybara.default_selector = :css
      Capybara.javascript_driver = :webkit
      ActionController::Base.allow_rescue = false
      begin
        DatabaseCleaner.strategy = :transaction
      rescue NameError
        raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
      end
      Cucumber::Rails::Database.javascript_strategy = :truncation
    
    end
    
    Spork.each_run do
      # This code will be run each time you run your specs.
    
    end
    
  5. Now it is time to boot Spork. This is done with the following command:

    > spork cuc
    Using Cucumber, Rails
      -- Rinda Ring Server listening for connections...
    
      -- Starting to fill pool...
         Wait until at least one slave is provided before running tests...
      ** CTRL+BREAK to stop Spork and kill all ruby slave processes **
    Spork is ready and listening on 8990!
       -- build slave 1...
       -- build slave 2...
    Preloading Rails environment
    Preloading Rails environment
    Loading Spork.prefork block...
    Loading Spork.prefork block...
      --> DRb magazine_slave_service: 1 provided...
      --> DRb magazine_slave_service: 2 provided...
    

    After some time, Spork will be ready to receive incoming test suites. This terminal will be occupied by this Spork process. Therefore you will have to open a new one to issue next commands.

  6. It is now time to run our Cucumber test suite using Spork test server. to do so, we invoke Cucumber with the --drb option:

    > cucumber --drb
    Using the default profile...
    Disabling profiles...
    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript                                     # features\test_cases\javascript.feature:4
        When I visit the page "/"                                                                  # features/step_definitions/main_steps.rb:1
        And I add a link "Click me to replace content" replacing "body" content with "New content" # features/step_definitions/main_steps.rb:9
        And I click "Click me to replace content"                                                  # features/step_definitions/main_steps.rb:19
        Then "body" content should be "New content"                                                # features/step_definitions/main_steps.rb:23
    
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features\test_cases\simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    2 scenarios (2 passed)
    6 steps (6 passed)
    0m21.407s
      <-- Slave(2) run done!
    

    If everything goes well, you should see the same output as before, but with a tremendous improvement concerning the time spent running your test suite: no more boot time.

Now we have a listening test server, ready to run our test suite as often as we need.

On my local Windows 7 (native), time improvements are clearly visible:

  • Without Spork: 102 secs
  • With Spork: 22 secs

Second step in continuous integration: listening to source changes with Guard

Next step is to automatically run our test suite as soon as some files have changed.
This way, developers won’t need to run the test suite manually, taking the risk to forget it.

A nice gem, named Guard is here to monitor a files system and take actions on modifications. This gem has nice plugins designed specifically for Spork, our test server: guard-spork, and for Cucumber: guard-cucumber.
These plugins will help generating the default configuration that will monitor files related to each of these services (ie restarting Spork when config changes, restarting Cucumber tests when Cucumber scenarios change).

/!\ At the time of this writing, Guard does not work on native Windows, due to this issue. If you are running a Windows system, I would recommend using Cygwin for Guard to work correctly. If you can’t use Cygwin, then you can try alternatives to Guard, such as Watchr or Autotest.

  1. Once again, we add gem dependencies in the :test group:

      # Guard
      gem 'guard'
      gem 'guard-cucumber'
      gem 'guard-spork'
    
  2. And once again, we install them:

    > bundle
    ...
    Using guard (1.2.3)
    ...
    Using guard-cucumber (1.2.0) 
    ...
    Using guard-spork (1.1.0)
    ...
    Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
    
  3. Then we need to extend Guard’s configuration file with each plugin’s default configuration:

    > bundle exec guard init spork
    Writing new Guardfile to /path/to/rails_app/Guardfile
    spork guard added to Guardfile, feel free to edit it
    
    > bundle exec guard init cucumber
    cucumber guard added to Guardfile, feel free to edit it
    
  4. We need to alter the generated Guard configuration file to tell Cucumber it has to run using Spork. This is done by adding the :cli => '--drb' option:

    guard 'cucumber', :cli => '--drb' do
      watch(%r{^features/.+\.feature$})
      watch(%r{^features/support/.+$})          { 'features' }
      watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
    end
    
  5. It is now time to launch a Guard instance:

    > bundle exec guard start
    Guard uses NotifySend to send notifications.
    Guard is now watching at '/path/to/rails_app'
    Starting Spork for RSpec, Cucumber
    Using Cucumber, Rails
    Using RSpec, Rails
    /path/to/rails_app/spec/spec_helper.rb has not been bootstrapped.  Run spork --bootstrap to do so.
    Preloading Rails environment
    Loading Spork.prefork block...
    Spork is ready and listening on 8990!
    Preloading Rails environment
    Spork is ready and listening on 8989!
    Spork server for RSpec, Cucumber successfully started
    Running all features
    Using the default profile...
    Running tests with args ["--require", "/path/to/gems/guard-cucumber-1.2.0/lib/guard/cucumber/notification_formatter.rb", "--format", "Guard::Cucumber::NotificationFormatter", "--out", "/dev/null", "--require", "features", "features", "--format", "pretty", "--strict", "--tags", "~@wip", "features", "--no-profile"]...
    Disabling profiles...
    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript                                     # features/test_cases/javascript.feature:4
        When I visit the page "/"                                                                  # features/step_definitions/main_steps.rb:1
        And I add a link "Click me to replace content" replacing "body" content with "New content" # features/step_definitions/main_steps.rb:9
        And I click "Click me to replace content"                                                  # features/step_definitions/main_steps.rb:19
        Then "body" content should be "New content"                                                # features/step_definitions/main_steps.rb:23
    
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features/test_cases/simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    2 scenarios (2 passed)
    6 steps (6 passed)
    0m17.172s
    Done.
    
    > 
    

    You can see in the output that just after booting, Guard has started Spork, then run the Cucumber tests suite. You should also have received some system notifications, stating that:

    1. Spork was booted correctly
    2. Your Cucumber tests have passed successfully

    Notes:

    • /!\ At the time of this writing, this command will not succeed on native Windows.
    • Please note that you should first kill (using Ctrl-C) the previous Spork instance that was run during this tutorial: it becomes useless once we use Guard.
    • If you happen to run through an error stating Could not start Spork server for RSpec, Cucumber, 2 things might help you:
      1. Try adding a :wait parameter to the Guard configuration for Spork:
        guard 'spork', :wait => 60, :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
        
      2. Try removing the eventual test/ folder that might be in your Rails application (rename it if you prefer).

    Now it is time to check that the Cucumber tests are run upon some files modifications.

  6. First, modify, save or just touch one of the features file. For this tutorial, I issue a touch features/test_cases/simple.feature.
    And look at the output of the Guard process:

    Running Cucumber features: bundle exec cucumber --drb --require /path/to/gems/guard-cucumber-1.2.0/lib/guard/cucumber/notification_formatter.rb --format Guard::Cucumber::NotificationFormatter --out /dev/null --require features features/test_cases/simple.feature
    Using the default profile...
    Running tests with args ["--require", "/path/to/gems/guard-cucumber-1.2.0/lib/guard/cucumber/notification_formatter.rb", "--format", "Guard::Cucumber::NotificationFormatter", "--out", "/dev/null", "--require", "features", "features/test_cases/simple.feature", "--format", "pretty", "--strict", "--tags", "~@wip", "--no-profile"]...
    Disabling profiles...
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features/test_cases/simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    1 scenario (1 passed)
    2 steps (2 passed)
    0m8.071s
    Done.
    
    > 
    

    You can see that Guard has automatically relaunched Cucumber (using Spork), and running just the Simple.feature scenarios (as it was the only file being modified).
    A system notification has also been sent to tell you that those 2 steps have passed successfully.

  7. Then, do the same on a configuration file that might affect your Rails environment (and therefore the running Spork instance).
    For this tutorial, I issued a touch config/application.rb.
    Look at the Guard output:

    Reloading Spork for RSpec, Cucumber
    Using Cucumber, Rails
    Using RSpec, Rails
    /path/to/rails_app/spec/spec_helper.rb has not been bootstrapped.  Run spork --bootstrap to do so.
    Preloading Rails environment
    Loading Spork.prefork block...
    Spork is ready and listening on 8990!
    Preloading Rails environment
    Spork is ready and listening on 8989!
    Spork server for RSpec, Cucumber successfully reloaded
    > 
    

    You can see that the Spork instance has been rebooted, also along with a system notification.

  8. Usually you also want to execute your test suite as soon as application files are modified. By default Guard for Cucumber does not include Rails application files to be monitored. Time to add them to the Guard configuration file:

    guard 'cucumber', :cli => '--drb' do
      watch(%r{^features/.+\.feature$})
      watch(%r{^features/support/.+$})          { 'features' }
      watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
      # Add Rails application files
      watch(/^app\/.+$/) { 'features' }
      watch(/^public\/.+$/) { 'features' }
    end
    
  9. Then, after a touch app/views/layouts/application.html.rb, look at the Guard output:

    Running all features
    Using the default profile...
    Running tests with args ["--require", "/path/to/gems/guard-cucumber-1.2.0/lib/guard/cucumber/notification_formatter.rb", "--format", "Guard::Cucumber::NotificationFormatter", "--out", "/dev/null", "--require", "features", "features", "--format", "pretty", "--strict", "--tags", "~@wip", "features", "--no-profile"]...
    Disabling profiles...
    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript                                     # features/test_cases/javascript.feature:4
        When I visit the page "/"                                                                  # features/step_definitions/main_steps.rb:1
        And I add a link "Click me to replace content" replacing "body" content with "New content" # features/step_definitions/main_steps.rb:9
        And I click "Click me to replace content"                                                  # features/step_definitions/main_steps.rb:19
        Then "body" content should be "New content"                                                # features/step_definitions/main_steps.rb:23
    
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features/test_cases/simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    2 scenarios (2 passed)
    6 steps (6 passed)
    0m14.726s
    Done.
    
    > 
    

    You can check that all tests have been run, along with system notification. I will leave it up to you to better fine tune your Guard configuration file.

Finally you have a running test server, listening to your changes in your source code repository, and executing a comprehensive and maintainable test suite.

You should be all set to tackle large Rails application test suites with those great tools.

Summary

Here I recap all the commands to execute and the files to edit to get the whole stack up and running.

  1. Add gem dependencies
    group :test do
    
      # RSpec
      gem 'rspec'
      gem 'rspec-rails'
    
      # Cucumber
      gem 'cucumber-rails', :require => false
      gem 'database_cleaner'
    
      # Capybara-webkit
      gem 'capybara-webkit'
    
      # Spork
      gem 'spork-rails'
    
      # Guard
      gem 'guard'
      gem 'guard-cucumber'
      gem 'guard-spork'
    
    end
    
  2. Install gems
    > bundle
    
    Fetching gem metadata from https://rubygems.org/.......
    ...
    Installing gherkin (2.11.1)
    Installing cucumber (1.2.1)
    Installing cucumber-rails (1.3.0)
    Installing database_cleaner (0.8.0)
    Installing rspec-core (2.11.0)
    Installing rspec-expectations (2.11.1)
    Installing rspec-mocks (2.11.1)
    Installing rspec (2.11.0)
    Installing rspec-rails (2.11.0)
    ...
    Using capybara-webkit (0.12.1)
    ...
    Using spork (1.0.0rc3)
    Using spork-rails (3.2.0)
    ...
    Using guard (1.2.3)
    ...
    Using guard-cucumber (1.2.0) 
    ...
    Using guard-spork (1.1.0)
    ...
    Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
    
  3. Prepare your test database if needed
    > rake db:migrate
    > rake db:test:load
    
  4. Generate RSpec and Cucumber configuration files:
    > rails generate rspec:install
          create  .rspec
          create  spec
          create  spec/spec_helper.rb
    

    and

    > rails generate cucumber:install
          create  config/cucumber.yml
          create  script/cucumber
           chmod  script/cucumber
          create  features/step_definitions
          create  features/support
          create  features/support/env.rb
           exist  lib/tasks
          create  lib/tasks/cucumber.rake
            gsub  config/database.yml
            gsub  config/database.yml
           force  config/database.yml
    
  5. Modify Capybara’s configuration to use Webkit driver
    Capybara.javascript_driver = :webkit
    
  6. Generate Spork configuration:
    > spork cucumber --bootstrap
    Using Cucumber, Rails
    Bootstrapping /path/to/rails_app/features/support/env.rb.
    Done. Edit /path/to/rails_app/features/support/env.rb now with your favorite text editor and follow the instructions.
    
  7. Edit environment file to adapt it to Spork:
    require 'rubygems'
    require 'spork'
    #uncomment the following line to use spork with the debugger
    #require 'spork/ext/ruby-debug'
    
    Spork.prefork do
      # Loading more in this block will cause your tests to run faster. However,
      # if you change any configuration or code from libraries loaded here, you'll
      # need to restart spork for it take effect.
    
      require 'cucumber/rails'
      Capybara.default_selector = :css
      Capybara.javascript_driver = :webkit
      ActionController::Base.allow_rescue = false
      begin
        DatabaseCleaner.strategy = :transaction
      rescue NameError
        raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
      end
      Cucumber::Rails::Database.javascript_strategy = :truncation
    
    end
    
    Spork.each_run do
      # This code will be run each time you run your specs.
    
    end
    
  8. Generate Guard configuration file, for Spork and Cucumber:
    > bundle exec guard init spork
    Writing new Guardfile to /path/to/rails_app/Guardfile
    spork guard added to Guardfile, feel free to edit it
    
    > bundle exec guard init cucumber
    cucumber guard added to Guardfile, feel free to edit it
    
  9. Modify the Guard configuration file to add Spork support to Cucumber, and monitor Rails application files
    guard 'cucumber', :cli => '--drb' do
      watch(%r{^features/.+\.feature$})
      watch(%r{^features/support/.+$})          { 'features' }
      watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
      # Add Rails application files
      watch(/^app\/.+$/) { 'features' }
      watch(/^public\/.+$/) { 'features' }
    end
    
  10. Write your test files:
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code
        When I visit the page "/"
        Then the response code should be "200"
    

    and

    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript
        When I visit the page "/"
          And I add a link "Click me to replace content" replacing "body" content with "New content"
          And I click "Click me to replace content"
        Then "body" content should be "New content"
    
  11. Implement Cucumber test definitions:
    When /^I visit the page "(.*?)"$/ do |iPagePath|
      visit iPagePath
    end
    
    Then /^the response code should be "(.*?)"$/ do |iResponseCode|
      page.status_code.should == iResponseCode.to_i
    end
    
    When /^I add a link "(.*?)" replacing "(.*?)" content with "(.*?)"$/ do |iLinkName, iElementSelector, iNewContent|
      page.execute_script("
        var lLinkElement = document.createElement('a');
        lLinkElement.setAttribute('onclick','$(\\'#{iElementSelector}\\')[0].innerHTML = \\'#{iNewContent}\\'');
        lLinkElement.setAttribute('href', '#');
        lLinkElement.innerHTML = '#{iLinkName}';
        $('body')[0].appendChild(lLinkElement);
      ")
    end
    
    When /^I click "(.*?)"$/ do |iLinkName|
      click_on iLinkName
    end
    
    Then /^"(.*?)" content should be "(.*?)"$/ do |iElementSelector, iContent|
      find(iElementSelector).should have_content(iContent)
    end
    
  12. Launch a Guard instance:
    > bundle exec guard start
    Guard uses NotifySend to send notifications.
    Guard is now watching at '/path/to/rails_app'
    Starting Spork for RSpec, Cucumber
    Using Cucumber, Rails
    Using RSpec, Rails
    /path/to/rails_app/spec/spec_helper.rb has not been bootstrapped.  Run spork --bootstrap to do so.
    Preloading Rails environment
    Loading Spork.prefork block...
    Spork is ready and listening on 8990!
    Preloading Rails environment
    Spork is ready and listening on 8989!
    Spork server for RSpec, Cucumber successfully started
    Running all features
    Using the default profile...
    Running tests with args ["--require", "/path/to/gems/guard-cucumber-1.2.0/lib/guard/cucumber/notification_formatter.rb", "--format", "Guard::Cucumber::NotificationFormatter", "--out", "/dev/null", "--require", "features", "features", "--format", "pretty", "--strict", "--tags", "~@wip", "features", "--no-profile"]...
    Disabling profiles...
    Feature: JavaScript testing
    
      @javascript
      Scenario: Arriving on home page and executing JavaScript                                     # features/test_cases/javascript.feature:4
        When I visit the page "/"                                                                  # features/step_definitions/main_steps.rb:1
        And I add a link "Click me to replace content" replacing "body" content with "New content" # features/step_definitions/main_steps.rb:9
        And I click "Click me to replace content"                                                  # features/step_definitions/main_steps.rb:19
        Then "body" content should be "New content"                                                # features/step_definitions/main_steps.rb:23
    
    Feature: Simple testing
    
      Scenario: Arriving on home page gives a 200 response code # features/test_cases/simple.feature:3
        When I visit the page "/"                               # features/step_definitions/main_steps.rb:1
        Then the response code should be "200"                  # features/step_definitions/main_steps.rb:5
    
    2 scenarios (2 passed)
    6 steps (6 passed)
    0m17.172s
    Done.
    
    > 
    

I hope this tutorial will help you have a great environment to test your Rails applications with.

What are your thoughts ?

About Muriel Salvan

I am a freelance project manager and polyglot developer, expert in Ruby and Rails. I created X-Aeon Solutions and rivierarb Ruby meetups. I also give trainings and conferences on technical topics. My core development principles: Plugins-oriented architectures, simple components, Open Source power, clever automation, constant technology watch, quality and optimized code. My experience includes big and small companies. I embrace agile methodologies and test driven development, without giving up on planning and risks containment methods as well. I love Open Source and became a big advocate.
Cygwin, Howto, Ruby, Ruby on Rails, Web development, Windows , , , , , , , , , , , ,

4 comments


Leave a Reply

Your email address will not be published.