Optimize CI Using a Strong Testing Suite with Ruby on Rails

Written by: Evan Glazer

When it comes to testing suites, it's imperative to have a strong suite with all the tools to make your teams life easier. Once your team has a strong testing suite, automating the suite with continuous integration (CI) will bring less defects in your production environments. Currently, the industry has some best practices for testing: BDD, TDD, etc. - although the question in hand - is writing tests before or after development a good approach? We’re going to talk about test driven development; which goes over the methodology to WHY writing tests before we develop is efficient.

I will also discuss some of the necessity’s in Ruby on Rails for making a solid testing suite using the most popular testing framework. I will walk through testing concepts and discuss some of the tools out there, how to configure the testing suite, and give some tips on how to write tests with best practices.

What is TDD?

Test driven development is the approach, first, the test is developed which validates what the code will do. Then, write and correct the failed tests before writing new code. This helps to avoid duplication of code as we write a small amount of code at a time in order to pass tests. From my experience, it is a solid approach as it gets the developer thinking about the smaller sub problems verse overthinking or building out the whole problem - then refactoring for hours.

Where do we start? RSPEC!!!

In this article, we will use RSPEC

, which is a strong unit testing framework that allows us to connect other powerful tools to make our testing suite easy to write tests in.

Awesome Tools for our testing suite!

Timecop

  • Provides the ability for us to write tests that time "travel" and "freeze" for specific situations. Also provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.

Database Cleaner

  • Provides the ability for us to clean up our test blocks and not hold any previous data per context block - this depends on your strategy.

Shoulda Matchers

  • Offers simple one-liner tests for common Rails functionality.

Factory Bot

  • Provides a very useful tool to create our models and be able to have different types of traits that go with user case stories.

Faker

  • Provides a library that allows us the ability to have fake random types of data for our factory models, etc.

Capybara

  • Helps test web applications by simulating how a real user would interact with your app.

Pry

  • Provides us the ability to debug our tests - similar to the rails console in our testing suite.

Concepts to know!

Double. A way for you to mock up model data.

Stubs. Mocks of data to answer to calls made during the test.

Factory. Mocks for developers to be able to create, build, etc. modeled data with traits in our testing suite to replace doubles.

Mock. Idea to recreate an object with fake data with the specification of the calls they are expected to receive.

Fixtures. Ability to have large files of data segmented into folders and read inside the suite with a cleaner way to read in data.

Describe. Main block of testing that is clear about what method is being referred to. This would be: Validations, #GET index, Callbacks, etc.

Context. Multiple blocks inside the describe block; although this should make your tests clear and well organized.

It. Block that specifically announces what the value should return and should not exceed more than 40 characters.

Before do. Opportunity before the blocks run to create instance variables, populate data or a specific task.

After do. Opportunity after the blocks run to create instance variables, populate data or a specific task.

Best Practices

5 Tips

  • Use let or let! Instead of adding instance variables with before do callback.

  • Keep your descriptions short.

  • Use shared examples to DRY your test suite.

  • Test what you see ONLY.

  • Do not use should when describing tests.

I would refer to betterspec.org for a full guide to best rspec practices.

Setup my RSPEC Config with "Awesome Tools"

Explain Spec/Rails

Spec Helper is for specs which don't depend on Rails, which could be custom spec classes. Rails Helper is for specs which do depend on Rails, which would be our tools. In rspec best practices, rails_helper.rb requires spec_helper.rb.

Below shows how you can begin configuring your spec files correctly using RSPEC.

rails_helper.rb

This file is copied to spec/ when you run rails generate rspec:install

require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE&lt__)

Prevent database truncation if the environment is in production.

abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|

Remove this line if you're not using ActiveRecord or ActiveRecord fixtures.

config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :active_record
    with.library :active_model
    with.library :action_controller
    with.library :rails
  end
end
config.before(:suite) do
  DatabaseCleaner.clean_with(:truncation)
end
config.after(:all) do
  DatabaseCleaner.clean_with(:truncation)
end
config.include Rails.application.routes.url_helpers
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end

spec_helper.rb

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

Conclusion

Once we have a strong testing suite, continuous integrations is our best friend. We can further optimize our suite by setting up hooks to automatically run our tests on pre-deploy states, automate our suite for deployments, etc.

Having CI setup with a service like CodeShip will enable you to make the deployment processes less likely to have defects in your production environments enabling faster development.

Additional resources

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.