We have recently started the development of a new application for a new client. One of the features requires the user to be able to select words in an input that, under the hood, uses the Select2 tagging system.
Writing a test to validate that this feature works would look something like this:
create_tags_in_database sign_in user visit a page when I type "ta" in my tag_list field and I choose the tag "tag1" in my tag_list field And press save Then my record tag_list should contains "tag1"
Since we are using Rails 5.1 and the latest rspec-rails, we are now able to use system tests with Rspec. And since this particular test relies on JavaScript they are a very good candidate.
While trying to get this test to work, I encountered a few quirks, mainly my created records not being available in my system tests and trying to select something inside select2.
Let’s dive into how to solve them.
Prerequisite
First, you will need to have the latest rspec-rails
and capybara
versions.
You will need to install 2 new gems in your :test group
gem "chromedriver-helper", group: :test gem 'selenium-webdriver', group: :test
Loading different drivers
Now we will create a way to load different drivers, so when we’re running system tests that won’t require JavaScript we will use the fastest rack server, and when we need javascript we will use Selenium chrome headless. Create a new file in spec/support/system/driver.rb:
# spec/support/system/driver.rb RSpec.configure do |config| config.before(:each, type: :system) do driven_by :rack_test end config.before(:each, type: :system, js: true) do ActiveRecord::Base.establish_connection driven_by :selenium_chrome_headless end end
A new system test file would now look something like this:
require 'rails_helper' RSpec.describe "Creating a post with tags", type: :system do end
And you need JavaScript to be enabled – you will just need to add a js: true to the describe block.
require 'rails_helper' RSpec.describe "Creating a post with tags", type: :system, js: true do end
If at that stage, you get complaints like “cannot find :selenenium_chrome_headless” please make sure you have the latest capybara version.
And, if you’re using Devise, make sure that you add this line to your rails_spec.rb config:
config.include Devise::Test::IntegrationHelpers, type: :system
But, I can’t view my record on the page!
If you’re using Puma, and you were writing a test like this…
require 'rails_helper' RSpec.describe "Creating a post with tags", type: :system, js: true do context "visiting the new post page" do it "shows me a list of tags" do Tag.create(name: "tag1") visit new_post_page expect(page).to have_content("tag1") end end end
… then there is a big chance that your test will fail. The reason is that the Tag record is created in another thread.
To remedy that, you need to ensure that Puma starts in 0 workers mode and 1 thread only.
Our current Puma config looks like this:
# config/puma.rb workers Integer(ENV.fetch("WEB_CONCURRENCY", 2)) threads_count = Integer(ENV.fetch("MAX_THREADS", 2)) threads(threads_count, threads_count) preload_app! rackup DefaultRackup environment ENV.fetch("RACK_ENV", "development") on_worker_boot do # Worker specific setup for Rails 4.1+ # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot ActiveRecord::Base.establish_connection end
You now need to create a support/puma.rb file:
ENV["WEB_CONCURRENCY"] = "0" ENV["MAX_THREADS"] = "1"
Now, you should have a green test!
Let’s select a tag with Select2 in Capybara
Select2 hijacks your normal form select[multiple=true] and creates a fake input driven by JavaScript. So I have created a small helper and put it in support/helpers/select2_choose_tag.rb
def select2_choose_tag(field_class, options = {}) within(".form-group.#{field_class}") do first('.select2-container', minimum: 1).click first('.select2-search__field').send_keys(options.fetch(:choose, ""), :enter) end end
Now your final test can look like this:
require 'rails_helper' RSpec.describe "Creating a post with tags", type: :system, js: true do context "visiting the new post page" do it "shows me a list of tags" do Tag.create(name: "tag1") visit new_post_page fill_in "Title", with: "Rspec & System tests are fun" select2_choose_tag "tag_list", choose: "tag1" click_on "save" expect(Post.last.tag_list).to eql(["tag1"]) end end end
Closing thoughts
System tests are now the defaults in Rails, and the Rspec team recommends that you move your feature tests to system tests. Doing so is fairly easy. You will just need to amend the describe block to include type: :system and change .feature for a .describe.
You also need to make sure to only load js: true when you really need JavaScript and allow for non-JavaScript dependent tests to remain as snappy as possible.