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:
- Introduction
- Prerequisite for this tutorial
- Ruby and Rails environment
- OS
- First: helpers to write readable tests: RSpec and Cucumber
- Test your website using a real browser engine: Capybara and Webkit
- First step in continuous integration: having a running instance forking the Rails environment: Spork
- Second step in continuous integration: listening to source changes with Guard
- 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:
-
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
-
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.
-
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
-
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
-
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"
-
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.
-
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.
-
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.
-
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
.
-
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.
-
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
.
-
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.
-
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
-
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
-
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!
-
As usual we start by adding the Spork gem dependency in the Gemfile (still the
:test
group):
# Spork
gem 'spork-rails'
-
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.
-
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.
-
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
-
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.
-
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.
-
Once again, we add gem dependencies in the
:test
group:
# Guard
gem 'guard'
gem 'guard-cucumber'
gem 'guard-spork'
-
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.
-
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
-
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
-
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:
- Spork was booted correctly
- 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:
- 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
- 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.
-
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.
-
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.
-
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
-
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.
- 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
- 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.
- Prepare your test database if needed
> rake db:migrate
> rake db:test:load
- 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
- Modify Capybara’s configuration to use Webkit driver
Capybara.javascript_driver = :webkit
- 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.
- 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
- 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
- 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
- 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"
- 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
- 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 ?