Integration testing can be intimidating to some, the idea of having to build test cases for the way your controllers are communicating with your view and model could make the best of us scratch our heads.
But these are actually the easiest test cases to build and in this post, you’ll find some of the tricks I used to not scratch my head that much.
For starters we need to know the definition of integration testing, Albert Einstein once said:
“once you know exactly what to do you already solved 90% of your problem.”
He didn’t quite say it that way but let us jump to the definition, shall we?
This articles’ definition fits the most with what I’m trying to say:
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.
Even though you need your model, controller, and view, by the time you get to testing integration your application would already have a basic workflow (even if you used TDD) and if there is some feature your application is lacking off, just like unit testing, integration testing will help you fill in this gaps and build a solid workflow.
So without further ado let’s talk about the workflow
This is a simple exercise lot’s of us ignore but to know the workflow of an application we have to yes you got it use it!
When building test cases you are not just the programmer but the user as well, think of an application like let's say twitter what is the first thing it pops up when you open the browser with twitter I think you got it but here is an image
The login and signup are the first workflow features you’ll see in many applications and the test cases for them are relatively easy to build.
Up next I’ll describe a couple of test cases I build for a Twitter-based project I made while I was a student in Microverse. I will only focus on a couple of them because most of them are repetitive so I think it’s enough for you to build awesome test cases as well.
Rspec and Capybara are both Ruby gems for testing so I figure you have your own Ruby on Rails application and some unit test cases for it working already, if not you can clone or download my Workagram! and you’ll find instructions on how to run the app and the test cases on your computer (thank you for leaving a star in the project if you like it!).
Like I said before to keep going you need to have Rspec and Capybara already installed so if you do you can skip this step but if something is not working properly here is how I installed and configured both Rspec and Capybara
I followed this article to install and configure both Rspec and Capybara if you already have Rspec still you’d need to change some configuration files.
I won’t share the commands here but this is how my configuration files look like at the end so make sure yours do too:
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
.
.
.
group :development, :test do
.
.
.
gem 'rspec-rails'
end
.
.
.
group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
.
end
.
.
.
Gemfile.rb
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
# have no way to turn it off -- the option exists only for backwards
# compatibility in RSpec 3). It causes shared context metadata to be
# inherited by the metadata hash of host groups and examples, rather than
# triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = 'spec/examples.txt'
config.disable_monkey_patching!
config.default_formatter = 'doc' if config.files_to_run.one?
config.order = :random
Kernel.srand config.seed
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
# # This allows you to limit a spec run to individual examples or groups
# # you care about by tagging them with `:focus` metadata. When nothing
# # is tagged with `:focus`, all examples get run. RSpec also provides
# # aliases for `it`, `describe`, and `context` that include `:focus`
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
# config.filter_run_when_matching :focus
#
# # Allows RSpec to persist some state between runs in order to support
# # the `--only-failures` and `--next-failure` CLI options. We recommend
# # you configure your source control system to ignore this file.
# config.example_status_persistence_file_path = "spec/examples.txt"
#
# # Limits the available syntax to the non-monkey patched syntax that is
# # recommended. For more details, see:
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
# config.disable_monkey_patching!
#
# # This setting enables warnings. It's recommended, but in some cases may
# # be too noisy due to issues in dependencies.
# config.warnings = true
#
# # Many RSpec users commonly either run the entire suite or an individual
# # file, and it's useful to allow more verbose output when running an
# # individual spec file.
# if config.files_to_run.one?
# # Use the documentation formatter for detailed output,
# # unless a formatter has already been configured
# # (e.g. via a command-line flag).
# config.default_formatter = "doc"
# end
#
# # Print the 10 slowest examples and example groups at the
# # end of the spec run, to help surface which specs are running
# # particularly slow.
# config.profile_examples = 10
#
# # Run specs in random order to surface order dependencies. If you find an
# # order dependency and want to debug it, you can fix the order by providing
# # the seed, which is printed after each run.
# # --seed 1234
# config.order = :random
#
# # Seed global randomization in this process using the `--seed` CLI option.
# # Setting this allows you to use `--seed` to deterministically reproduce
# # test failures related to randomization by passing the same `--seed` value
# # as the one that triggered the failure.
# Kernel.srand config.seed
end
spec/spec_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
require 'capybara/rspec'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
puts e.to_s.strip
exit 1
end
Capybara.register_driver :selenium_chrome do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
Capybara.javascript_driver = :selenium_chrome
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = false
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
# Filter lines from Rails gems in backtraces.
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
# This block must be here, do not combine with the other `before(:each)` block.
# This makes it so Capybara can see the database.
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
spec/rails_helper.rb
remember to run ‘bundle install’ after modifying the Gemfile if still doesn’t work you can look for the complete Gemfile in my Workagram! repository
If your application supports users this is the most important and the first feature to test, because all the other features of your application will depend on a user to be logged in to be able to access them.
Many applications would take a username and a password to log in a user but my Workagram! login only requires the Username, the code for this test case is pretty straight forward:
RSpec.describe 'login followed by logout', type: :feature do
# The user must exist so we are creating it here
let(:user) { User.create(username: 'maruk', fullname: 'Maria Eugenia Queme') }
scenario 'login page' do
# First we need to be in the root path
visit root_path
# Secondly fill in the fields in this case only the username
fill_in 'session_username', with: user.username
# Third we have to click the Log in button
click_button 'Log in'
# We give it some time to load
sleep(3)
# And lastly we are expecting our page to have the success message
expect(page).to have_content('User Logged in')
# Logging out it's much simplier after this
click_on 'Log out'
sleep(3)
expect(page). to have_content('Please log in')
end
end
I’ve put comments to guide you through it but what I like the most about Capybara is that the commands are plain English instructions as you can see: visit, fill_in, click_button can’t get any easier than that!
Awesome tip #1: sometimes we struggle knowing the names of the fields, how do we know we have to fill_in ‘session_username’ you’d ask, for that we can check it out with your browser’s developer tools inspecting the field and use what’s in the id selector.
After building the authentication test case you can test everything else in no particular order, anything that looks like a form or requires to fill in a field and/or has a submit button should be tested (i.e sign up form, edit users’ info, post something, follow users, unfollow users, etc..).
Lastly, I’m gonna show you how I build a test case to validate the posting feature of my Workagram!
I chose the posting feature to show next because of the posts in my Workagram! require an image similar to an Instagram post and I struggled a little when building this test case so I think these tips could be helpful for your own application, here is the code:
RSpec.describe 'Home management', type: :feature do
# We need to create an user to authenticate
let(:user) { User.create(username: 'maruk', fullname: 'Maria Eugenia Queme') }
# The image has to be saved somewhere in your project
let(:picture) { 'app/assets/images/1000.png' }
scenario 'Create valid work' do
# Log in the user
visit root_path
fill_in 'session_username', with: user.username
click_button 'Log in'
sleep(3)
# if succesfull we'll be in the main page
expect(page). to have_content('WORKAGRAM SOMETHING')
# We only need to attach a picture to post a job, the text is optional
attach_file 'work_picture', picture
# click on the Post button
click_button 'Post'
# We let the page load for a while
sleep(3)
# If everything went well it's expecting the success message to display on screen
expect(page). to have_content('Work created!')
# Also the main page would have to include the image of the post we just added
expect(page).to have_css("img[src*='1000.png']")
end
end
Now that we have the authentication test case we‘ll need to put the code for it in every feature of our application otherwise we would have authentication errors.
Awesome tip #2: We also need a variable to hold the picture’s location (it’s better if it’s within your project so your test cases work in other computers)
Awesome tip #3: I use the “have_css” Capybara method when trying to validate anything that wasn’t plain text.
If you got this far I have to say you have enough to build your own test cases but if you still want to check out a couple more you can find them in my Workagram!
Til the next time claps to you!