When your Ruby on Rails project is getting bigger your test suite as well. You need to test more of your business logic and sometimes you will use other gems that can help you with that. Most of the time you may need something like database_cleaner , capybara for feature tests or rspec-sidekiq to test your workers.

Adding new gems needed for testing often requires changes in RSpec configuration. You add a new line of config here and there in the spec_helper.rb or rails_helper.rb file and suddenly you have huge and hard to understand config file for RSpec.

I will show you how I organize my RSpec configuration directory structure to easily add or modify the RSpec configuration in a clean way.

Prepare RSpec config directory structure

I keep all of my configuration code related to RSpec in directory spec/support/config . You can create it.

Next step is to ensure the RSpec will read files from the config directory. In order to do it please ensure you have below line in your spec/rails_helper.rb file.

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

By default, it is commented out so please uncomment it.

Separate config files for different testing gems

Here I will show you examples of popular gems and how to keep their configuration clean. A few examples are on the video and many more code examples are in this article.

Configuration for Database Cleaner

For database_cleaner gem you can just create config file spec/support/config/database_cleaner.rb :

# spec/support/config/database_cleaner.rb require 'database_cleaner' RSpec . configure do | config | config . before ( :suite ) do DatabaseCleaner . strategy = :deletion DatabaseCleaner . clean_with ( :deletion ) end config . around ( :each ) do | example | DatabaseCleaner . cleaning do example . run end end end

Configuration for Capybara

Here is my configuration of Capybara placed at spec/support/config/capybara.rb .

# spec/support/config/capybara.rb require 'capybara/rspec' JS_DRIVER = :selenium_chrome_headless DEFAULT_MAX_WAIT_TIME = ENV [ 'CI' ] ? 5 : 2 Capybara . default_driver = :rack_test Capybara . javascript_driver = JS_DRIVER Capybara . default_max_wait_time = DEFAULT_MAX_WAIT_TIME RSpec . configure do | config | config . before ( :each ) do | example | Capybara . current_driver = JS_DRIVER if example . metadata [ :js ] Capybara . current_driver = :selenium if example . metadata [ :selenium ] Capybara . current_driver = :selenium_chrome if example . metadata [ :selenium_chrome ] Capybara . default_max_wait_time = example . metadata [ :max_wait_time ] if example . metadata [ :max_wait_time ] end config . after ( :each ) do | example | Capybara . use_default_driver if example . metadata [ :js ] || example . metadata [ :selenium ] Capybara . default_max_wait_time = DEFAULT_MAX_WAIT_TIME if example . metadata [ :max_wait_time ] end end

I have a few custom things here like easy option to switch between different browsers executing my tests by just adding tag like :selenium_chrome to test:

# spec/features/example_feature_spec.rb it 'something' , :selenium_chrome do visit '/' expect ( page ). to have_content 'Welcome' end

You can learn more how to configure Capybara with Chrome headless here.

Configuration for Sidekiq

I like keep my Sidekiq configuration with other useful Sidekiq related gems in one place spec/support/config/sidekiq.rb :

# spec/support/config/sidekiq.rb require 'rspec-sidekiq' require 'sidekiq/testing' require 'sidekiq_unique_jobs/testing' RSpec . configure do | config | config . before ( :each ) do Sidekiq :: Worker . clear_all # https://github.com/mperham/sidekiq/wiki/Testing#testing-worker-queueing-fake if RSpec . current_example . metadata [ :sidekiq_fake ] Sidekiq :: Testing . fake! end # https://github.com/mperham/sidekiq/wiki/Testing#testing-workers-inline if RSpec . current_example . metadata [ :sidekiq_inline ] Sidekiq :: Testing . inline! end end config . after ( :each ) do if RSpec . current_example . metadata [ :sidekiq_fake ] || RSpec . current_example . metadata [ :sidekiq_inline ] Sidekiq :: Testing . disable! end end end RSpec :: Sidekiq . configure do | config | config . warn_when_jobs_not_processed_by_sidekiq = false end

I use gems like sidekiq-unique-jobs and rspec-sidekiq. Here you can read more about Sidekiq testing configuration.

Configuration for FactoryBot (known as FactoryGirl)

FactoryBot config file for Ruby on Rails can be isolated at spec/support/config/factory_bot.rb :

# spec/support/config/factory_bot.rb RSpec . configure do | config | config . include FactoryBot :: Syntax :: Methods end

Configuration for JSON Spec

If you have API endpoints in your application you may like the json_spec gem that can help you test JSON responses. Here is my config at spec/support/config/json_spec.rb

# spec/support/config/json_spec.rb require 'json_spec' RSpec . configure do | config | config . include JsonSpec :: Helpers end

Configuration for RSpec Retry

Sometimes big projects have painful tests that randomly fail. The last rescue when we cannot make them stable and always green is to retry them a few times before marking them as failed. This is one option how to deal with flaky tests and we can use for that rspec-retry gem.

# spec/support/config/rspec_retry.rb RSpec . configure do | config | # show retry status in spec process config . verbose_retry = true # show exception that triggers a retry if verbose_retry is set to true config . display_try_failure_messages = true # run retry only on features config . around :each , :js do | ex | # retry test 3 times on CI but do not retry when testing locally ex . run_with_retry retry: ( ENV [ 'CI' ] ? 3 : 1 ) end # callback to be run between retries config . retry_callback = proc do | ex | # run some additional clean up task - can be filtered by example metadata if ex . metadata [ :js ] Capybara . reset! end end end

Here you can learn more about how to deal with flaky tests:

Configuration for Shoulda Matchers

Shoulda Matchers provides RSpec and Minitest-compatible one-liners that test common Rails functionality. Here is my config spec/support/config/shoulda_matchers.rb :

# spec/support/config/shoulda_matchers.rb require 'shoulda/matchers' Shoulda :: Matchers . configure do | config | config . integrate do | with | # Choose a test framework: with . test_framework :rspec #with.test_framework :minitest #with.test_framework :minitest_4 #with.test_framework :test_unit # Choose one or more libraries: #with.library :active_record #with.library :active_model #with.library :action_controller # Or, choose the following (which implies all of the above): with . library :rails end end

Configuration for VCR and WebMock

You can record your requests in testing with VCR or mock request with WebMock. This is my config spec/support/config/vcr.rb :

# spec/support/config/vcr.rb require 'vcr' VCR . configure do | config | config . cassette_library_dir = 'spec/fixtures/vcr_cassettes' config . hook_into :webmock # or :fakeweb config . allow_http_connections_when_no_cassette = true config . ignore_hosts ( 'localhost' , '127.0.0.1' , '0.0.0.0' , 'example.com' , ) end WebMock . disable_net_connect! ( allow: [ 'example.com' , ])

Configuration for Knapsack Pro to split test suite across parallel CI nodes

If you have a large test suite taking a dozen of minutes or maybe even hours then you may want to run tests in parallel across multiple CI node with Knapsack Pro to get the fastest CI build.

This is example config file.

# spec/support/config/knapsack_pro.rb require 'knapsack_pro' KnapsackPro :: Adapters :: RSpecAdapter . bind

Here you can learn more about how it works.

Configuration for Page Objects

You can keep your page objects in a separate directory. Page objects can be later reused in multiple tests. Create directory spec/support/page_objects . Here is example page object for billing page:

# spec/support/page_objects/billing_page.rb class BillingPage def self . fill_company_details ( first_name: 'Kayleigh' , last_name: 'Johnston' , company: 'Example Company' , vat_id: 'UK1234567890' , email: 'kayleigh.johnston@example.com' , street_address: '59 Botley Road' , locality: 'Midtown' , region: '' , # can be blank postal_code: 'IV27 5LL' , country_name: 'United Kingdom' , website: 'http://example.com' ) Capybara . fill_in 'first_name' , with: first_name Capybara . fill_in 'last_name' , with: last_name Capybara . fill_in 'company' , with: company Capybara . fill_in 'vat_id' , with: vat_id Capybara . fill_in 'email' , with: email Capybara . fill_in 'street_address' , with: street_address Capybara . fill_in 'locality' , with: locality Capybara . fill_in 'region' , with: region Capybara . fill_in 'postal_code' , with: postal_code Capybara . select country_name , from: 'country_name' Capybara . fill_in 'website' , with: website end end

Then you can use the page object in your feature spec.

# spec/features/billing_spec.rb it 'fills company details on billing page' do BillingPage . fill_company_details end

Configuration for shared examples

You can organize your RSpec shared examples in directory spec/support/shared_examples that will be autoloaded.

Summary