My journey from Rspec feature tests to Rails system tests
Tags: rails, rspec, testing
Published: 2017-07-13 00:30:41

---
title: My journey from Rspec feature tests to Rails system tests
published: true
description: A story of failure getting consistent headless feature tests, to trying again with many different ways to make rspec work, then converting to Rails system tests in Rails 5.1.
tags: Ruby on Rails, rspec, testing
---

#Goals:
* Achieve reliable headless testing with system tests that utilize javascript
* Learn the new Rails system test setup

Quite often I find myself spending hours debugging(and sometimes finally deleting) a flaky feature test.  Finally I decided to stop doing that, and after watching the Rails Conf [talk](https://www.youtube.com/watch?v=sSn4B8orX70) about system testing, decided it was time to act.

Since my first goal is to always keep it simple, I didn't jump right away into the Rails system tests.  Due to the rest of my testing suite being written in Rspec, I first tried to go headless with that setup.

#Rspec failure(s)
##Trying [headless chrome with capybara](https://robots.thoughtbot.com/headless-feature-specs-with-chrome)

###spec/rails_helper.rb
```ruby
Capybara.register_driver :headless_chrome do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    chromeOptions: { args: %w(headless disable-gpu) }
  )

  Capybara::Selenium::Driver.new app,
    browser: :chrome,
    desired_capabilities: capabilities
end

Capybara.javascript_driver = :headless_chrome
```

###Result:
* Selenium::WebDriver::Error::unknownError...basically it gave me the 'is not clickable at point' error.  I tried working around these by doing simple fixes to capybara finders, digging more for answers, and in the end(about 30 minutes), couldn't get it resolved.  So I moved on to the next attempt... 

##Trying [capybara webkit](https://github.com/thoughtbot/capybara-webkit)

###Result:
* Well this didn't go very far as it needed a [Qt](https://github.com/thoughtbot/capybara-webkit#qt-dependency-and-installation-issues) dependency, and when I tried to brew install that, the compiler barfed due to, what seems to be, Xcode upgrade dependency.  Due to not having admin privileges on my mac, I took the easy way out and abandoned webkit for now. 

##Trying [poltergeist](https://github.com/teampoltergeist/poltergeist)

###spec/rails_helper.rb
```ruby
require 'capybara/poltergeist'

Capybara.javascript_driver = :poltergeist
```

###Modifying some tests for known 'click_on' [issues] (https://github.com/teampoltergeist/poltergeist/issues/530):
####Before:
```ruby
click_on 'Create Engine'
```
####After:
```ruby
find("input[type=submit][value='Create Engine']").trigger('click')
```

###Result:
* Success! ...or so I thought.  All features passed.  However, when ran with entire test suite, they randomly failed.  So I tried fixing them with different swap-outs for 'have_content' checks and such.  Those then passed, ran again, they failed and the others(that I didn't fix) passed.  At this point I was thoroughly frustrated, because either I run my test suite for features separately from other tests, or I need to fix this...
* Note: I *assume* this has to due with the database cleanup and different AR threads and such that I've read about.  I did have my database cleaner setup to truncate on js tests, but when ran in headless mode with the rest of my test suite, it was flaky.  Also, before going down this headless path, they worked fine.

Being fed up with flaky tests and 3 failed attempts at remedying this in Rspec, I decided to now move on and try the Rails System Tests...

#Rails System Tests:
###Setup:
####Prerequisites: 
* Rails 5.1
* [Poltergeist](https://github.com/teampoltergeist/poltergeist)
* [Mocha](https://github.com/freerange/mocha)
* [Puma](https://github.com/puma/puma)
* Some other gems I like: FactoryGirl/Capybara(of course), standard test setup

####test/test_helper.rb
* Simple setup really, only added a few helpers and such:
```ruby
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'mocha/test_unit' # for stubs
require 'minitest/spec' # for let/it/etc

# not sure about this, but rails kinda does it here...feels sloppy, but I hate
# typing require in all my test files
Dir[Rails.root.join("test/support/**/*.rb")}.each { |f| require f }

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
  extend MiniTest::Spec::DSL
  include WaitForAjax # hack for remote js calls that waits for ajax to be done
end
```
####test/application_system_test_case.rb
* This file comes with Rails 5.1, but I switched it to use poltergeist:
```ruby
require 'test_helper'
require 'capybara/poltergeist'

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :poltergeist
end
```
####test/system/app/my_map_test.rb
* This is just one of the test files, but good enough to show the setup
* I like to abstract away a lot of the standard Capybara DOM traversal steps, so those will be in the next file.
```ruby
require 'application_system_test_case'

class MyMapTest < ApplicationSystemTestCase
  class StandardType < MyMapTest
    let(:subject) { MyMapForm.new('standard_type') }
    
    test 'page has different types' do
      subject.visit_page
      
      assert has_content?('first type')
      assert has_content?('second type')
      assert has_content?('third type')
    end

    test 'page has address link' do
      subject.visit_page
      assert has_link?('Address Import')
    end
  end
end
```
Note: this was originally written in Rspec, but the conversion was simple...
* s/describe/class, s/it/test, s/expect..to/assert, s/have_content/has_content?..etc

####test/support/forms/my_map_form.rb
* This is my file to hold all the repeatable/dirty details about traversing the DOM
```ruby
class MyMapForm
  include Capybara::DSL
  include FactoryGirl::Syntax::Methods
  include Rails.application.routes.url_helpers

  def initialize(type)
    # using factory here of course and converting to find the factory
    @item = create(type.to_sym)
    # one more...
    create(type.to_sym)
    @app = item.app
  end

  def visit_page
    # don't care about authenticating here
    # in rspec, this uses allow_any_instance_of...
    ApplicationController.any_instance.stubs(:require_long).returns(true)
    ApplicationController.any_instance.stubs(:can_edit?).returns(true)
   
    visit app_path(app)
    find('a', test: 'My Map').click # in non headless, click_on 'My Map' works
    self
  end

  private 
  
  attr_reader :item, :app
end
```

# Conclusions:

##Pros:
* It seems to work so far...and let's be honest, that is the most important thing.
* Default functionality of a screenshot in my terminal on failure.  Basic, but nice.
* Seems to solve the multiple db connection issue, and therefore reducing the flakiness of these gui driven system tests
* Runs faster than non headless implementation
* I suppose it makes running tests in CI env easier, as we can skip some of the browser setup?
* Gives the team some exposure to Rails test framework (this can also be seen as a con below)
* By using a different test suite for system tests, it gives a clear line of delineation in configs and troubleshooting, etc.
* Is part of Rails core, so I have full confidence in the future support and improvements

##Cons:
* Adds technical debt to the team since we do everything else in Rspec
* Documentation/examples for Rspec is miles ahead from what I've been able to find for Rails System tests (hence some of the reason for this writeup)

##Final Thoughts:

While this journey into Rails System tests feels good today, it is quite possible I will look back in a month or so and wonder...what was I thinking using this and multiple test suites...  But then again, don't we always look back and have those thoughts about our development decisions?

Of note, I also see that [rspec-rails](https://github.com/rspec/rspec-rails), is planning on supporting this soon as well, I think, as seen by this [issue](https://github.com/rspec/rspec-rails/pull/1813). If that comes to fruition, it will be worth a look, and definitely worth consolidating back into one test suite...we'll see.