Narnach's blog

The thoughts of Wes "Narnach" Oldenbeuving

System tests in rails 6 + rspec + vcr + capybara

Getting this setup working took me an hour of searching the web to get some of the interactions working, so I hope this helps you do it faster and avoid some pitfalls I stumbled into.

Summary

This post describes some of the obstacles I ran into getting proper JS-supported system tests working in my up-to-date Rails app, Infinity Feed.

It used to be complex to get Capybara working with a JS-enabled headless browser for your integration tests. This has gotten significantly easier with Rails 5.x and got another boost with 6.x.

If your knowledge about Rails frontend testing still stems from the Rails v4 or v5 era, you might be pleasantly surprised by how easy it can be now.

Setting the scene

For this blog post, the relevant bits of my stack are:

  • capybara 3.35
  • faraday 1.4
  • rails 6.1
  • rspec 3.10
  • rspec-rails 5.0
  • turbo-rails 0.5
  • vcr 6.0
  • webdrivers 4.6
  • webmock 3.13

You’ve got the usual suspects here that test Rails with RSpec. I use unit tests for models and other plain old Ruby objects (POROs), there are controller tests for specific interactions and there was a feature test intended to test my frontend interactions. This is done differently in the modern era by using system tests.

I use Faraday for HTTP calls, so VCR + Webmock is part of my test stack to intercept & record HTTP calls so they can be replayed without hitting the network during my tests. This makes tests more consistent and faster. Win-win!

I had a feature test written with out-of-the-box Capybara which appeared to work just fine, until I wanted to test that Hotwire Turbo was doing its magic to make automatic JS-powered HTTP calls to replace my HTML with new HTML. The JS did not get executed in my tests!

So, I had to figure out how to enable JS. Remembering how much of an ordeal this used to be, and how often the best practices changed, I started searching the internet for how it is done in 2021.

System tests have replaced features

Since Rails 5.0/5.1 there are system tests, which are similar to the old feature tests we had, but now Rails handles all of the overhead you used to have to do yourself. You can stop futzing with DatabaseCleaner and configuring Puma to run in-process. It’s all taken care of by Rails now.

Since Rails 6, integration with browsers for testing happens via the webdrivers project, which handles downloading and updating the browser for you. It just works! Just beware of unexpected VCR interactions (see below).

Migrating features to system tests

It’s really easy:

  • Move feature files from spec/features/ to spec/system/
  • Change type: :feature to type: :system if needed for these files

Add this:

1
  before { driven_by :selenium_chrome_headless }

To the top of your system test files to pick a driver that supports JS. You can use this to configure different browsers and screen sizes.

And now you have working system tests!

There’s a few extra things left to configure if you need them.

Devise integration

If you use Devise to handle your authentication, you should register its integration test helpers to be used in feature and system tests:

1
2
3
4
5
6
7
# spec/rails_helper.rb

RSpec.configure do
  config.include Devise::Test::IntegrationHelpers, type: :feature
  config.include Devise::Test::IntegrationHelpers, type: :system
  # ...
end

This enables the very useful sign_in helper function, so your tests can focus on testing actual features instead of always having to simulate a login.

VCR integration

VCR helps you intercept HTTP calls during tests, as I mentioned earlier. The thing is that Webdrivers automatically checks if the latest version of the browser is installed and downloads it if needed. As you can imagine, this did not play well with VCR.

My solution is to force the webdriver to check for an update before I configure VCR. VCR also needs to be told not to interfere with system test calls.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# spec/rails_helper.rb

# RSpec.configure do
#   ...
# end

# Load this and update the driver before loading VCR.
# If you don't, VCR will intercept the version check.
require 'webdrivers'
Webdrivers::Chromedriver.update

# Configure VCR to don't interfere with system tests
VCR.configure do |config|
  # 127.0.0.1 is for system tests so whitelist it
  config.ignore_hosts '127.0.0.1'

  # My personal settings, feel free to ignore/change
  config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
  config.hook_into :webmock, :faraday
end

Conclusion

This worked for me ™, so I hope it helps you as well. If you run into issues, feel free to reach out to me via Twitter or email. Besides being close to launching a smart RSS reader over at Infinity Feed, I’m a freelance Ruby software developer with a focus on back-end systems and an obsession with code quality.