article How to test an autocomplete with Rails and Minitest?

29 Oct 2013
An autocomplete is a nice example for an ajax driven feature and I will demonstrate how to test such a features with Rails 4 and Minitest. First we will create a sample app and setup Minitest and Capybara for integration testing.
rails new rails-autocomplete-test
We need to add `capybara` and the `poltergeist` driver to our Gemfile:
group :development, :test do
  gem "capybara"
  gem 'poltergeist'
end
After we ran bundle we need make some changes to the `test_helper.rb`:
require "capybara/rails"

class ActionDispatch::IntegrationTest
  include Capybara::DSL
  require 'capybara/poltergeist'
  Capybara.javascript_driver = :poltergeist

  def teardown
    Capybara.current_driver = nil
  end
end
class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil
  def self.connection
    @@shared_connection || retrieve_connection
  end
end
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
First we require the `capybara/rails` module than set the javascript driver to `poltergeist`. I am also setting the current\_driver of capybara to `nil` after each test because I want to use poltergeist only when we are testing javascript features so the rest of the test suite can run faster. Than we make `ActiveRecord` to share the same connection between threads because capybara starts the browser in a different thread from the one our application uses and it wouldn't access to the same data in these threads. If you want to know more why we need do do this you can read Jose Valim's explanation on this link. Now we have a setup to test our javascript features so let's write a test for an autocomplete form field. We will expect a form with a search field on the root path:
require 'test_helper'

class AutocompleteTest < ActionDispatch::IntegrationTest

  test "autocomplete" do
    Capybara.current_driver = Capybara.javascript_driver
    visit "/"
    fill_in('search_keyword', with: 'Test')
  end
end
If we run the test it will fail of course:
1) Error:
AutocompleteTest#test_autocomplete:
Capybara::ElementNotFound: Unable to find field "search_keyword"
    test/integration/autocomplete_test.rb:8:in `block in <class:AutocompleteTest>'

1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
Let's fix this and generate a controller with an action and add the missing form to the view. We also need to set the root of our application to that action:
# config/routes.rb
root 'welcome#index'
rails g controller Welcome index
  create  app/controllers/welcome_controller.rb
   route  get "welcome/index"
  invoke  erb
  create    app/views/welcome
  create    app/views/welcome/index.html.erb
  invoke  test_unit
  create    test/controllers/welcome_controller_test.rb
  invoke  helper
  create    app/helpers/welcome_helper.rb
  invoke    test_unit
  create      test/helpers/welcome_helper_test.rb
  invoke  assets
  invoke    coffee
  create      app/assets/javascripts/welcome.js.coffee
  invoke    scss
  create      app/assets/stylesheets/welcome.css.scss
# app/views/welcome/index.html.erb
<%= form_for :search do |f| %>
  <%= f.label :keyword %>
  <%= f.text_field :keyword %>
<% end %>
Now our test is passing. Next step is to extend our test to fill the form with something and see the autocomplete with some suggestion. Before we go any further we need to decide which javascript library to use as we need to know the markup it will use. I chose twitter's typeahead for this tutorial. We can copy the typeahead.js to the vendor/assets/javascripts folder and than we just need to require it in the application.js file:
app/assets/javascripts/application.js
...
//= require typeahead
...
This library uses the following markup:
<span class="tt-dropdown-menu">
  {{#dataset}}
    <div class="tt-dataset-{{name}}">
      {{{header}}}
      <span class="tt-suggestions">
        {{#suggestions}}
          <div class="tt-suggestion">{{{html}}}</div>
        {{/suggestions}}
      </span>
      {{{footer}}}
    </div>
  {{/dataset}}
</span>
It means we need to look for a div.tt-suggestion element after we entered the text to the field and compare the text of the element with the desired string:
require 'test_helper'

class AutocompleteTest < ActionDispatch::IntegrationTest
  test "autocomplete" do
    Capybara.current_driver = Capybara.javascript_driver
    visit "/"
    field = 'search_keyword'
    fill_in('search_keyword', with: 'Test')
    page.execute_script %Q{ $('##{field}').trigger("focus") }
    suggestion = page.find('div.tt-suggestion')
    assert_equal "Test", suggestion.text
  end
end
After we filled in the field we need to trigger the `focus` event. Of course this test fails in the moment so let's make it pass. First step is to setup a route and an action for the source of the suggestions:
# config/routes.rb
match 'suggestions' => 'welcome#suggestions', via: :get
# app/controller/welcome_controller.rb
def suggestions
  render json: [{name: 'Test'}]
end
We just render a hash as json for the sake of simplicity. Than we add some coffee to initialize typeahead:
$ ->
  $('#search_keyword').typeahead [
    {
      name: 'name'
      remote: {
        url: '/suggestions.json?q=%QUERY'
      }
      valueKey: 'name'
    }
  ]
Now if we run the test it will pass. I hope you learned how to test your javascript features with capybara and minitest. You can view the code of the sample application on this link.

Job listings

Post a Job!

Did you enjoy reading this? Follow me on Twitter or sign up to my newsletter for more content like this!

I run an indie startup providing vulnerability scanning for your Ruby on Rails app.

It is free to use at the moment, and I am grateful for any feedback about it.
If you would like to give it a spin, you can do it here: Vulnerability Scanning for your Ruby on Rails app!

Related posts