Testing The User Perspective With Ruby On Rails With Kameleon

Testing The User Perspective With Ruby On Rails With Kameleon

Just because nobody complains doesn’t mean all parachutes are perfect

– Benny Hill.

WHY TEST THE USER PERSPECTIVE WITH RUBY ON RAILS?

null

A month ago we published an article about the value of automated testing. The post was focused on describing various ways how automated testing can help with ensuring quality of your Ruby on Rails application. With the article that follows we move our attention from discussing pros of automated testing to discussing best practices for implementing automated testing in a Ruby on Rails application.

First, I will demonstrate how to setup basic automated test suite environment for a Ruby on Rails platform. Next, we will write a single end-to-end test which goal will be to ensure that our RoR application works as expected, from the user perspective. The test will also validate features integration within the system, telling us if the user can achieve his / her goals by using the application interface. Developers call such features integration test a “happy path A “happy path” it is usually a scenario which takes a virtual user for a journey through the application features, focusing on those that are expected to be most used. Furthermore, a “happy path” test scenario gives confidence that the system works well under typical conditions.

After we finish writing the end-to-end test, we will add a few supportive tests to our test suite. The goal of supportive tests is to verify alternative flows and other important features which have not been covered by the “happy path” test, e.g. access control.

All mentioned benefits of end-to-end and supportive tests come from taking user perspective approach during the process of designing automated test suite.

PREPARATIONS

The application used for demonstrating automated testing from user perspective isa simple project / task management app written in Ruby on Rails. A “happy path” in this application could be described by a situation where an user logs in and creates a project. The code of aforementioned RoR application can be found at tests-with-kameleon repository. After cloning the repository you can write your own tests on the master branch. However, if you want to see the tests implemented along the lines advocated in this article, switch to the with-tests branch.

From among a number of ruby gems, I have chosen Kameleon to work with. Kameleon is basically a dsl for writing expressive acceptance and integration tests. Let me how we can setup our project / task management RoR application with Kameleon

Setup

Assuming that you have initialized the project and that you are using Bundler gem, the first thing you do is add the gems for the test environment. Add the following code in your Gemfile:

group :test, :development do
  gem 'rspec-rails'
end

group :test do
  gem 'kameleon' # dsl for high abstracted test writing, based on capybara
  gem 'factory_girl_rails' # fixtures replacement
  gem 'capybara' # dsl for test writing
  gem 'spork', '~>;; 1.0rc' # test server
  gem 'database_cleaner' #  set of strategies for cleaning your database
end

Then you run

bundle install

The Gems have been installed and now it’s time to configure them.

1. RSpec

To generate basic rspec files run the command:

rails generate rspec:install

2. spork

To bootstrap your test helper file, run the command:

spork rspec --bootstrap

Next add a –drb parameter to the .rspec file. This will enable the tests to be executed faster thanks to a spork server.

You can now complete the spork configuration by editing the spec/spec_helper.rb. The configuration used for the purpose of this article looks like this:

require 'rubygems'
require 'spork'

Spork.prefork do
  ENV\["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", \_\_FILE\_\_)
  require 'rspec/rails'
  require 'kameleon/ext/rspec/all'

  Dir[Rails.root.join("spec/support/*_/_.rb")].each {|f| require f}

  RSpec.configure do |config|
    config.mock_with :rspec
    config.include FactoryGirl::Syntax::Methods
    config.before(:all) do
      DatabaseCleaner.clean_with(:truncation)
    end
  end
end

Spork.each_run do
end

The code above is responsible for requiring Rubygems, Spork, RSpec, Kameleon and some other helpers, which are placed in the support folder. In the RSpec.configure block we pass the configuration parameters for RSpec. We use config.before(:all) block to indicate that the database should be cleaned once before running examples from a certain group.

3. Create fixtures (optional)

For the purpose of this article I have decided to use the Factory Girl gem to make fixtures. Our spec/factories.rb file looks like this:

FactoryGirl.define do
  factory :user do
    email 'some.user@example.com'
    password 'secret'
  end

  factory :project do
    active true
  end
end

4. Set up the test database

Run

RAILS_ENV=test rake db:setup

in command line

5. Run tests

Run spork in the first terminal window and, after it has been loaded, run rspec spec in the second one. If everything goes well, you should see “No examples found (…) 0 examples, 0 failures“. It’s time to write the first test.

6. Write an end-to-end test

In our end-to-end scenario we will create three users (Stewie, Chris and Peter), who will sign in into the application. Stewie will create a project and then a task which will be delegated to Chris. Chris will update the task progress and delegate task to Peter for feedback. Peter will close the task. At the end Stewie will remove the closed task, rename the project and, finally, delete it.

In order to create multiple sessions for the test, I have added some helper methods in the spec/support/sessions.rb file:

def create_users(config)
  @session_to_user_attributes_map ||= {}
  config.each do |session_name, attributes|
    @session_to_user_attributes_map\[session_name] = attributes
    create(:user, attributes)
  end
end

def create_sessions
  @session_to_user_attributes_map.each do |name, user_attributes|
    act_as(name) do
      visit new_user_session_path
      fill_in user_attributes\[:email] =>;;'Email', user_attributes\[:password] =>;;'Password'
      click 'Sign in'
    end
  end
end

In this file: createusers method will create User objects and establish a capybara session for each of the user objects. \We should run it once, before all the tests in the group**. The createsessions method simply logs in all previously defined users. It should be run before each example.

Now, let’s create the spec/requests/endtoend_spec.rb file, and write some tests:

require 'spec_helper'

describe 'Application' do
  before(:all) do
    create_users({
                     :stewie =>;; {:email =>;; 'stewie.griffin@example.com', :password =>;; 'secret'},
        :chris =>;; {:email =>;; 'chris.griffin@example.com', :password =>;; 'secret'},
        :peter =>;; {:email =>;; 'peter.griffin@example.com', :password =>;; 'secret'}
    })
  end

  before(:each) { create_sessions }

  it 'allows users to complete project with many tasks' do

    # creating project
    act_as(:stewie) do
      click 'Projects', 'New project'
      fill_in 'Ruby Project' =>;; 'Name', :check =>;; 'Active'
      click 'Create Project'
      see 'Project created!', 'Ruby Project'
    end

    # creating task
    act_as(:stewie) do
      click 'Projects', 'Ruby Project', 'New task'
      fill_in 'Setup test env' =>;; 'Name',
          'Use kameleon with rspec' =>;; 'Description',
          :select =>;; { 'chris.griffin@example.com' =>;; 'User'},
          :choose =>;; 'High'
      click 'Create Task'
      see 'Task created!'
      within('table') do
        see 'Setup test env', 'New'
      end
    end

    # update task
    act_as(:chris) do
      visit root_path
      click 'Setup test env'
      see 'Status: New', 'Progress: 0%', 'Use kameleon'
      click 'Edit task'
      fill_in 'I have installed rspec, spork, capybara and kameleon. Please review configuration.' =>;; 'Comment',
          :select =>;; { 'peter.griffin@example.com' =>;; 'User', '50%' =>;; 'Progress', 'Opened' =>;; 'Status'}
      click 'Update Task'
      see 'Task updated!'
      within('table') do
        see 'Setup test env', 'Opened'
      end
    end
    
    # update task #2
    act_as(:peter) do
      visit root_path
      click 'Setup test env'
      see 'Status: Opened', 'Progress: 50%', 'Use kameleon', 'I have installed rspec'
      click 'Edit task'
      fill_in 'It is correct. I am closing this task' =>;; 'Comment', :select =>;; { 'stewie.griffin@example.com' =>;; 'User', '100%' =>;; 'Progress', 'Closed' =>;; 'Status'}
      click 'Update Task'
      see 'Task updated!'
      within('table') do
        see 'Setup test env', 'Closed'
      end
    end

    # remove task, update project, remove project
    act_as(:stewie) do
      visit root_path
      click 'Projects', 'Ruby Project', 'Setup test env'
      click :and_accept =>;; 'Remove task'
      see 'Task destroyed!'
      not_see 'Setup test env'
      click 'Edit project'
      fill_in 'PROJECT TO DESTROY' =>;; 'Name',
          :uncheck =>;; 'Active'
      click 'Update Project'
      see 'Project updated!'
      click 'PROJECT TO DESTROY'
      click :and_accept =>;; 'Remove project'
      see 'Project destroyed!'
      not_see 'PROJECT TO DESTROY'
    end
  end
end

First, we have to require spechelper.rb. In the describe ‘End to end test’ block we use a before(:all) block to create users and establish capybara sessions for them. We log in each user created in the before(:each) block. In the it ‘works’ block we make assertions in order to verify if the data displayed to users is correct. The actsas blocks allow us to work with many sessions without the necessity to sign out / sign in each time we need it.

The test is composed of steps written in Kameleon DSL. As you can see, calling one method with multiple parameters usually allows us to perform a number of actions or assertions. The technique requires significantly less code than in case of the same test written in Capybara.

7. Run the tests again

Restart spork in the first terminal window (it has to load added spec/support/*.rb files) and run rspec spec in the second terminal window again. If everything goes well, you should see “1 example, 0 failures“. Well done!

8. Write supportive tests

When we have a passing end-to-end test at our disposal, it is a good idea to develop a few supportive tests to make sure that alternative flows and other features work fine as well. I have created two groups of examples – one connected with projects and the other with tasks. In the case of projects, I would like to test three things:

– if managers can see their projects on the dashboard,

– if validations work correctly during project creation,

– if access control works correctly for project management;

As regards tasks, there are also three things I would like to test:

– if the user assigned to a certain task can see his task on the dashboard,

– if validations work correctly during task creation,

– if access control works correctly for task management.

Let’s do the coding!

File: spec/requests/projects_spec.rb
require 'spec_helper'

describe 'Project' do
  before(:all) do
    create_users({:stewie =>;; {:email =>;; 'stewie.griffin@example.com', :password =>;; 'secret'},
      :chris =>;; {:email =>;; 'chris.griffin@example.com',:password =>;; 'secret'}})
    @project = Project.create :name =>;; 'Some project', :manager =>;; User.find_by_email('stewie.griffin@example.com')
  end

  before(:each) { create_sessions }

  it 'should be displayed in managed list for its creator' do
    act_as(:stewie) do
      visit root_path
      see 'Some project'
    end
    act_as(:chris) do
      visit root_path
      not_see 'Some project'
    end
  end

  it 'must have a name and user assigned' do
    act_as(:stewie) do
      visit root_path
      click 'Project', 'New project', 'Create Project'
      within('#project_name_input') { see 'can't be blank' }
    end
  end

  it 'should be editable only for project manager' do
    act_as(:stewie) do
      visit edit_project_path(@project)
      not_see 'Access denied'
    end
    act_as(:chris) do
      visit edit_project_path(@project)
      see 'Access denied'
    end
  end
end

File: spec/requests/tasks_spec.rb
require 'spec_helper'

describe 'Task' do
  before(:all) do
    create_users({:stewie =>;; {:email =>;; 'stewie.griffin@example.com', :password =>;; 'secret'},
      :chris =>;; {:email =>;; 'chris.griffin@example.com', :password =>;; 'secret'}, 
      :peter =>;; {:email =>;; 'peter.griffin@example.com', :password =>;; 'secret'}
    })
    @project = Project.create :name =>;; 'Some project', :manager =>;; User.find_by_email('stewie.griffin@example.com')
    @task = @project.tasks.create :name =>;; 'Testing task', :user =>;; User.find_by_email('chris.griffin@example.com')
  end

  before(:each) { create_sessions }

  it 'should be displayed in delegated list for assigned user' do
    act_as(:stewie) do
      visit root_path
      not_see 'Testing task'
    end
    act_as(:chris) do
      visit root_path
      see 'Testing task'
    end
  end

  it 'must have a uniq name and assigned user' do
    act_as(:stewie) do
      visit root_path
      click 'Project', 'Some project', 'New task', 'Create Task'
      within('#task_name_input') { see 'can't be blank' }
      within('#task_user_input') { see 'can't be blank' }
    end
  end

  it 'should be editable only for project manager or assigned user' do
    act_as(:stewie) do
      visit edit_project_task_path(@project, @task)
      not_see 'Access denied'
    end
    act_as(:chris) do
      visit edit_project_task_path(@project, @task)
      not_see 'Access denied'
    end
    act_as(:peter) do
      visit edit_project_task_path(@project, @task)
      see 'Access denied'
    end
  end
end

9. Re-run the tests

Restart spork in the first terminal window (it has to load added spec/support/*.rb files) and, subsequently, run rspec spec in the second terminal window again. If everything goes well, you should see “7 examples, 0 failures“. Very good, again!

Conclusion

Thus we have developed a nice set of automated tests for a simple Ruby on Rails application. Thanks to a good combination of RSpec and Kameleon, we have managed to write an expressive end-to-end test as well as six supportive tests in no time at all. All the examples written use a virtual browser, which has an influence on the execution time of the whole test suite. Your tests should execute fast. When you have a large number of tests and you do not have enough time to wait to for the full feedback, use tags to execute only end-to-end tests. As mentioned, their purpose is to ensure that the application will work correctly for the user under typical conditions.

Post Scriptum

Special thanks to Radek Jędryszczak and Michał Czyż for their feedback on this article.