Search code examples
ruby-on-railsrspeccapybarathin

What is the proper way to test a Rails controller's action that is using throw :async?


I have a Rails server running on Thin and an async controller action. I would like to test it using RSpec, but I am getting an error:

 Failure/Error: get :access_token
     ArgumentError:
       uncaught throw :async

This is a sample code:

class SampleController < ApplicationController

  def sample
    EM.defer do
      render json: { response: 'Hello World' }

      request.env['async.callback'].call response
    end

    throw :async
  end

end

require 'spec_helper'

describe SampleController, :type => :controller do
  it "hello world" do
    get :sample
    expect(JSON.parse(response.body)[response]).to eq('Hello World')
  end
end

The reason why I get this error is because :async can only be handled by Thin server. There is no Thin server available in a vanilla Rails controller.

So tried Capybara:

  describe "GET /sample", type: feature do
    it "hello world" do
      visit sample_path

      expect(JSON.parse(page.body)['response']).to eq('Hello World')
    end
  end


  # In spec_helper.rb
  Capybara.server do |app, port|
    require 'rack/handler/thin'
    Rack::Handler::Thin.run(app, :Port => port)
  end

But I still get the same error. I believe it is because Thin needs to be started in threaded mode; and capybara does no start it in such a way.

What is the proper way to test a controller's action that is using throw :async?

The action does work when I use a regular browser to get to it.


Solution

  • In order to test an asynchronous Ruby on Rails action implemented with Thin you will need to run the test using Thin. Otherwise it will fail, or be brittle – in case you try mocking things up.

    So let's set it up using Capybara:

    In Gemfile:

    gem 'thin'
    gem 'capybara'
    gem 'selenium-webdriver'
    

    In spec/rails_helper.rb:

    require 'capybara/rails' 
    
    Capybara.default_driver = :selenium
    
    Capybara.server do |app, port|
      require 'rack/handler/thin'
      Rack::Handler::Thin.run(app, :Port => port)
    end
    

    That sets the Capybara driver to be selenium, a real browser. The second part configures the Capybara server to be Thin.

    The test should be written then as this:

    describe SampleController, :type => :feature do
      it "my test" do
        visit sample_path
        expect(page).to have_content('Hello World')
      end
    end
    

    And this will make the test pass.