Integration testing ruby on rails with minitest and capybara

Integration Testing Ruby on Rails with Minitest and Capybara

Capybara is an acceptance test framework for web applications. Learn how to use it with Minitest for integration testing of your Ruby on Rails applications.

Cut your Rails test suite down to a few minutes with one-click automatic parallelization.

Automate parallelizing tests

Introduction

In this tutorial, we'll cover how to do integration tests in Rails using Minitest and Capybara. We'll also cover how integration tests can sometimes serve as a replacement for controller tests.

Prerequisites

To follow this tutorial, you'll need to have Ruby installed along with Rails. This tutorial was tested using Ruby version 2.3.3, Rails version 5.0.0.1, Minitest version 5.10.1, and Capybara version 2.11.1.

Currently, there are no known issues with using earlier or later versions of any of those, however there will be some differences.

To get started, you can use gem install rails, and you should be good to go.

gem install rails

What is Minitest?

Minitest is a complete testing suite for Ruby, supporting test-driven development (TDD), behavior-driven development (BDD), mocking, and benchmarking. It's small, fast, and it aims to make tests clean and readable.

If you're new to Minitest, you can take a look at our tutorial on getting started with Minitest.

Minitest is the default testing suite included by default with new Rails applications, so no further setting up is required to get it to work. Minitest and RSpec are the two most common testing suites used in Ruby. If you'd like to learn more about RSpec, you can read our tutorial on getting started with RSpec as well as this tutorial on mocking with RSpec: doubles and expectations.

What is Capybara?

Capybara is an acceptance test framework for web applications, often used to do end-to-end testing in Rails applications.

It allows developers to simulate a user on a web page, make assertions based on the content and environment of the page, and it also provides an API to interact with the web page.

By default, it will run in headless mode, but it can also use a browser or a number of other drivers instead.

What is Integration Testing?

While unit tests make sure that individual parts of your application work, integration tests are used to test that different parts of your application work together. They are normally used to test at least the most important workflows of applications.

Terminology

Although we refer to this kind of testing as integration testing, it might also be referred to as acceptance testing. The authors of Capybara refer to it as an acceptance testing framework. Sometimes, these kind of tests are also called end-to-end tests.

Integration Tests vs. Controller Tests

The default method for testing controllers in Rails is to use functional tests. However, since controllers are ideally very lean in Rails, and our integration tests are exercising all code paths, they can be omitted in favor of thorough integration tests.

For example, an integration test for a user signup can assert that the welcome email was sent out just as well as a functional controller test can.

Some people prefer to test controllers in isolation. Sometimes, there is a good reason to do this, if your controllers are very complex.

Setup

Provided you have installed Ruby and Rails, you can set up a new Rails application.

rails new integration-testing-minitest

We'll need to add Capybara to our Gemfile in the group test. This can be done at the bottom of the file. Note that we're using a gem called minitest-rails-capybara, which integrates Capybara with Minitest and Rails.

# Gemfile

...

group :test do
  gem 'minitest-rails-capybara'
end

We'll also need to load Capybara in order to use it in our tests.

# test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/rails/capybara"

...

We're now all set to start writing our example application.

Example Application

Now that we have everything set up, we'll introduce an example application where we make use of integration tests.

Our example application is a blog where we’ll have an index of posts and a link to a page for writing new posts. Each post has a title and a body, and every time a new post is created, an email is sent out to an admin to notify them about the new post.

Since we're focusing on integration testing, we'll use generators where we can, and avoid focusing much on other kinds of tests in order to keep this tutorial brief.

Let's start by running the scaffold generator to create posts.

rails generate scaffold Post title:string body:text

We've omitted the output here, it should create all the necessary files for posts. Next we need to migrate the database.

rake db:migrate
== 20161214213527 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0020s
== 20161214213527 CreatePosts: migrated (0.0024s) =============================

The generator created some tests for us, and although we are not going to be using those, it's a good idea to run them to make sure everything works.

rake

# Running:

.......

Finished in 1.103736s, 6.3421 runs/s, 8.1541 assertions/s.

7 runs, 9 assertions, 0 failures, 0 errors, 0 skips

You can also issue rails server at the command line and navigate to http://localhost:3000/posts to check the result. We're going to be using integration tests to automatically test all of this.

Integration Tests

Let's create our first integration test. We want to navigate to the index and make sure it lists all the blog posts in our database. Let's start by updating our fixtures so we can write a test for this.

# test/fixtures/posts.yml

one:
  title: Post Title One
  body: Post body one.

two:
  title: Post Title Two
  body: Post body two.

Here's our first integration test.

# test/integration/post_flow_test.rb

require 'test_helper'

class PostFlowTest < Capybara::Rails::TestCase
  def setup
    @one = posts :one
    @two = posts :two
  end

  test 'post index' do
    visit posts_path

    assert page.has_content?(@one.title)
    assert page.has_content?(@two.title)
  end
end

All we did here was visit the post index and make sure that the titles for both posts in our fixtures are present. Let's run all of our tests again to make sure they all pass. This test should pass without us having to do anything, since the scaffold we generated earlier does this already.

rake

# Running:

........

Finished in 0.515325s, 15.5242 runs/s, 21.3458 assertions/s.

8 runs, 11 assertions, 0 failures, 0 errors, 0 skips

Let move on to something a bit more complicated, and test that we can write a new post and submit it. Place it below the other test in our integration test for posts.

# test/integration/post_flow_test.rb

...

  test 'writing a new post' do
    visit posts_path

    click_on 'New Post'

    fill_in 'Title', with: 'Test Title'
    fill_in 'Body',  with: 'Test Body'

    click_on 'Create Post'

    assert_current_path post_path(Post.last)
    assert page.has_content?('Test Title')
    assert page.has_content?('Test Body')
  end
end

In this test, we navigate to the post index, click the link to write a new post, fill in the form with some text, and submit the post. We make sure that we're redirected to the right page and that it contains the text we wrote in the post.

Run the tests again to make sure everything passes. Again, we don't need to write any code since this test will already pass because of the code generated by the scaffold.

rake

# Running:

.........

Finished in 0.551475s, 16.3199 runs/s, 23.5731 assertions/s.

9 runs, 13 assertions, 0 failures, 0 errors, 0 skips

Now, we have one last feature to add, the email alert to an admin email once a post has been submitted. Let's start by adding a new test for writing a new post and checking if an admin notice email was sent.

# test/integration/post_flow_test.rb

require 'test_helper'

class PostFlowTest < Capybara::Rails::TestCase
  include ActiveJob::TestHelper

  def setup
    @one = posts :one
    @two = posts :two
  end

  test 'post index' do
    visit posts_path

    assert page.has_content?(@one.title)
    assert page.has_content?(@two.title)
  end

  test 'writing a new post' do
    write_new_post

    latest_post = Post.last

    assert_current_path post_path(latest_post)
    assert page.has_content?('Test Title')
    assert page.has_content?('Test Body')
  end

  test 'writing a new post sends admin notice' do
    perform_enqueued_jobs do
      write_new_post

      last_post = Post.last
      mail      = ActionMailer::Base.deliveries.last

      assert_equal 'admin@example.com', mail['to'].to_s
      assert_equal 'New post added', mail.subject
    end
  end

  private

  def write_new_post
    visit posts_path

    click_on 'New Post'

    fill_in 'Title', with: 'Test Title'
    fill_in 'Body',  with: 'Test Body'

    click_on 'Create Post'
  end
end

Quite a few things changed in this file. At the top, we include ActiveJob::TestHelper so we can tell ActiveJob to perform_enqueued_jobs before running our tests. This is necessary since we plan on using ActiveJob to deliver our email later, instead of doing so in the client request.

We also refactor writing a new post into a private method to keep the tests for writing a new post and for ensuring that the admin email gets sent separate. When it comes to integration tests, this is optional, you could also keep it all in the same test, and split it into methods.

Finally, we get the last email sent by ActionMailer and make sure it has the correct recipient and subject. We don't have to go further than that, since the content of the mail should be tested in the test for the mailer itself. This is to avoid testing the same thing in many places, and make our tests less rigid and easier to update.

Now, if you try running running our new tests, they'll fail since we haven't added the notification email yet. We'll be adding a mailer and calling it from PostsController#create and testing in our integration test instead of writing a separate functional test for the controller to do it.

Let's create the mailer first:

rails generate mailer PostMailer

This should set up the mailer. Now we need to add our admin notice email to it:

# app/mailers/post_mailer.rb

class PostMailer < ApplicationMailer
  def admin_notice(post)
    @post = post
    mail to: 'admin@example.com', subject: 'New post added'
  end
end

We also need the corresponding views:

<%# app/views/post_mailer/admin_notice.html.erb %>

<p>A new post has been added! Here's the post:</p>

<p><%= @post.title %></p>
<%= simple_format @post.body %>
<%# app/views/post_mailer/admin_notice.text.erb %>

A new post has been added! Here's the post:

Title: <%= @post.title %>
<%= @post.body %>

We'll skip the tests for this mailer to keep this tutorial from getting too long. All we have to do now is call the mailer from the controller after a post has been created.

# app/controllers/posts_controller.rb

...
  # POST /posts
  # POST /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        PostMailer.admin_notice(@post).deliver_later

        format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end
...

We added only one line there to call the mailer. Now, let's run our tests again and see if they pass.

rake

# Running:

..........

Finished in 0.975611s, 10.2500 runs/s, 15.3750 assertions/s.

10 runs, 15 assertions, 0 failures, 0 errors, 0 skips

All the tests should pass, and we now have an application that is integration tested end-to-end with Minitest and Capybara.

Conclusion

In this tutorial, we covered how to integration test Rails applications with Minitest and Capybara. We now have a functional Rails application that is covered with integration tests. After reading this tutorial, you should have an idea of how integration tests can be used to simulate a user within your Rails application, as well as how integration tests can in part, or fully, replace functional controller tests.

If you found this tutorial helpful, you might want to check out some of the other tutorials on testing in the Semaphore Community.

If you'd like to learn how to set up a BDD stack on a Rails 5 application, this tutorial is the best next read for you.

We'd also like to hear your comments and questions, so feel free to leave them in the section below.

P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We've recently published an ebook covering just that — "Rails Testing Handbook". Learn more and download a free copy.

51cee7009c304faca416b4475bd42446
Heidar Bernhardsson

I'm a London based Ruby consultant. Visit my website for more information.

on this tutorial so far.
User deleted author {{comment.createdAt}}

Edited on {{comment.updatedAt}}

Cancel

Sign In You must be logged in to comment.