Posts tagged "acceptance tests"

A while ago we were working on an application that had an entire version specially created for mobiles, such as the iPhone. This specific application was entirely tested with Capybara, Steak and Selenium Webdriver. Although the test suite wasn’t the fastest one in the world, the web application was very well tested, and to guarantee that we would also be testing the mobile version, we would have to simulate an iPhone user agent accessing the application.

But wait, you might be thinking that we are not able to change browser headers while dealing with Selenium. Capybara has a nice API to define new drivers and Selenium allows us to define different profiles with custom configurations for each driver. Lets see how we can put all this together to handle that:

Capybara.register_driver :iphone do |app|
  require 'selenium/webdriver'
  profile = Selenium::WebDriver::Firefox::Profile.new
  profile['general.useragent.override'] = "iPhone"
 
  Capybara::Driver::Selenium.new(app, :profile => profile)
end

Yup, it’s that simple =). We are creating a new driver for Capybara called :iphone, that will use Selenium with Firefox, but with a different profile, overriding the user agent string. This way you can pretend to your application that you are accessing through a “real” iPhone, by giving the “iPhone” string as user agent. You could also configure an :android driver, for instance, by simply changing the user agent string.

So now, how do we make use of that new driver in our specs? Here comes a simple example:

scenario 'access phone information using a modal box', :driver => :iphone do
  visit root_path
 
  page.should have_no_css "#fancybox-wrap"
  page.should have_no_content "0800 123456"
 
  within("header") { click_link "Telefones úteis" }
 
  within("#fancybox-wrap") do
    page.should have_content "0800 123456"
  end
end

We are just passing the :driver => :iphone option to our scenario. Remember that the latest Capybara versions use RSpec metadata options and will apply the :driver option automatically, changing the current driver to our registered :iphone in this case. For more info please refer to Capybara’s README.

You are now able to configure different user agents based on your application requirements, and test it in a full stack way. How about you, do you have any quick hint on how to test different user agents using another driver? Let us know in the comments :)

Updates (04/04/2014)

We were told that if you’re using Selenium Webdriver version 2.41.0, the code above will raise an exception. In order to fix that problem, you just need to replace Capybara::Driver::Selenium.new by Capybara::Selenium::Driver.new. Thanks Michael Joseph for suggesting that update.

One of the great gems released in the past few months was OmniAuth. It is very easy to use, it works without tons of configurations (unless configuring XML files is your thing) and there are already plenty of resources about it on the internet.

However, it is not easy to do acceptance tests with Omniauth as it depends on external services to work. So what should we do? When I face a scenario like this, I split the acceptance test in two parts: one before the external service and one after the external service response.

Testing the first one is trivial: you only have to ensure there is an <a> tag with href equals to “/auth/facebook” (or “/auth/#{insert_your_provider_here}” if you use another one). We don’t test any of the redirects done by OmniAuth internals, because it is already tested in gem’s tests, so we go to the next step: testing the OmniAuth callback.

Testing OmniAuth callbacks is in general cumbersome but for OAuth2 providers it is a bit easier as it uses Faraday internally to connect to the provider. With Faraday, we can configure a test adapter and stub calls to return what we want.

The OmniAuth strategy provides an entry point to the Faraday connection, but we don’t have an access to the strategy directly, so we need to store it globally. For a Facebook strategy, we can achieve it as below whenever configuring Omniauth middleware:

module OmniAuth
  mattr_accessor :facebook_strategy
end
 
middleware.use OmniAuth::Strategies::Facebook, "APP_ID", "APP_SECRET" do |strategy|
  OmniAuth.facebook_strategy = strategy
end

Note you will need OmniAuth 0.2.0 (which currently is OmniAuth master) as previous versions don’t yield a block on initialization like above.

With access to the strategy, we do the acceptance test. First, we define the hashes we will return on the Faraday responses.

FACEBOOK_INFO = {
  :id =>; '12345',
  :link => 'http://facebook.com/plataformatec',
  :email => 'myemail@example.com',
  :first_name => 'Plataforma',
  :last_name => 'Tecnologia',
  :website => 'http://blog.plataformatec.com.br'
}
 
ACCESS_TOKEN = {
  :access_token => "nevergonnagiveyouup"
}

Now, we will define the stubs. OAuth2 strategies do two requests: one to retrieve the access token and another to retrieve the user information. In this example, let’s stub the Facebook requests and assign these stubs to a new connection.

stubs = Faraday::Adapter::Test::Stubs.new do |b|
  b.post('/oauth/access_token') { [200, {}, ACCESS_TOKEN.to_json] }
  b.get('/me?access_token=nevergonnagiveyouup') { [200, {}, FACEBOOK_INFO.to_json] }
end
 
OmniAuth.facebook_strategy.client.connection = Faraday::Connection.new do |builder|
  builder.adapter :test, stubs
end

For each provider the URLs may differ, so an idea is to do this on a TDD way (or you can browse through the OmniAuth source code and see the url that it requests):

  1. Assign the Faraday fake connection without stubs.
  2. Run your test
  3. See the test to raise an exception like “No stubbed request for DESIRED_URL”.
  4. Add the stubbed request with the response that you want.
  5. Repeat this process until the test pass

This is what we do on acceptance tests with OmniAuth: testing before and after the access to the external services.

Another approach is to do only one test by short-circuiting the provider authentication URLs. To do that on a Rails application, you can store the provider URL on a method like “OmniAuth.facebook_url” and stub the method to return the callback URL on your test. If you happen to be using Devise, the upcoming 1.2 version does the short-circuiting automatically for you, as you can see in Devise integration tests.

What about you? How do you stub OmniAuth requests and responses on your applications?