Posts tagged with rspec

Stubbing CONSTANTS with RSpec

There's not any great info out there about stubbing constants. In fact, you can't stub constants. What you can do is reinitialize them.

The following code is trouble because Ruby complains about reinitializing the constant:

it 'changes the constant' do
  MyClass::SOME_CONSTANT = [1,2,3]
  MyClass::SOME_CONSTANT.should == [1,2,3]
end

#=> warning: already initialized constant SOME_CONSTANT

The spec passes but I hate the warning message Ruby spits out. So here's how we can prevent this warning and achieve the same result:

it 'changes the constant' do
  MyClass.send(:remove_const, 'SOME_CONSTANT')
  MyClass::SOME_CONSTANT = [1,2,3]
  MyClass::SOME_CONSTANT.should == [1,2,3]
end

We explicitly remove the constant before reinitializing it. We use #send to access #remove_const because it's a private method. Happy stubbing!

Posted by Mike Pack on 02/21/2012 at 01:15PM

Tags: rspec, stubbing


Seeding data to your Rails tests with factory_girl

Many applications rely on seed data as a basic set of information to get their app off the ground and functional. Seed data to an application is like wheels to a car. Without the wheels, your car won't function.

The concept of seed data works great when thinking about how the applications functions in the real world. However, when running tests against your application, it poses the question of "Well, where did the data come from?" It's like the philosophical question of the chicken or the egg.

You have a test database ready to rock. You might even use rake db:test:prepare to get it set up. Now you run your specs and uh oh! they fail because your seed data doesn't exist. Lets review some solutions to this problem.

Just seed the test database!

Stop with all the fuss and just seed the test database. rake db:seed and your test will be happy.

The problem with seeding your test database is you're now creating a dependency, and a large one at that. Every time you plan to execute your tests on a fresh database, you need the additional step of seeding. Not only that, but your tests are written to rely on an outside source to provide the seed data.

Your tests should viably run in a bubble. Give them an empty database and some application code and they should run to completion. Seeding the test database is an anti-pattern, in my opinion.

Enter the seed data when appropriate

Say you have a User model and after the user is created, they are given a gold star award for signing up. Assume your awards are stored in an awards table with an Award model and are seeded into your application.

When your tests rely on seed data, they might look like this (using factory_girl):

describe 'after signing up as a user'
  before do
    @user = Factory(:user)
  end

  it 'it gives me a gold star award' do
    @user.has_award('gold_star')
  end
end

In the above example, we're relying upon the fact that the gold star award has already been seeded into the database.

When your tests don't rely on seed data, they might look like this:

describe 'after signing up as a user'
  before do
    # Add the gold star award
    Factory(:award, :name => 'gold_star')
    @user = Factory(:user)
  end

  it 'it gives me a gold star award' do
    @user.has_award('gold_star')
  end
end

Now you've successfully removed the dependency on the seed data and brought the responsibility of database inadequacies to the test suite. This is a great step forward in refactoring the tests but now, every time you create a User with Factory(:user), you need to add the gold star award to the database. Lets fix this up.

Let factory_girl do the heavy lifting

factory_girl comes built in with a few callbacks. The available callbacks are #after_build, #after_create and #after_stub. Read more about using factory_girl callbacks on thoughtbot's blog (updated docs).

So, in lieu of adding the gold star award right before creating a User in every one of our tests, lets use a callback when creating a User.

FactoryGirl.define do
factory :user do
  name "Mike Pack"
  after_build do |user|
     Factory(:award, :name => 'gold_star') unless Award.find_by_name('gold_star').present?
  end
end
end 

Now, our tests can be as clean as possible:

describe 'after signing up as a user'
  before do
   @user = Factory(:user)
  end

  it 'it gives me a gold star award' do
    @user.has_award('gold_star')
  end
end

Every time a user is created with factory_girl, the dependency is built using the after_build callback.

Happy testing!

Posted by Mike Pack on 12/22/2011 at 03:01PM

Tags: rails, rspec, testing, factory_girl


Snowday Released

I just released an RSpec formatter to make you feel all warm inside. Like hot chocolate. It's called snowday.

Posted by Mike Pack on 11/20/2011 at 12:03PM

Tags: ruby, rspec, formatter


Stubbing Internal Methods and Rails Associations with RSpec

Occasionally, times arise where you would like to unit test the inner workings of a method. As a disclaimer, I don't recommend it because tests should generally be behavior driven. Tests should treat your methods as black boxes; you put something in, you get something out. How it works internally shouldn't really matter. However, if you would like to test the inner workings of your methods there's a number of ways to do so with pure Ruby including :send, :instance_variable_get and others. Testing the innards feels dirty no matter which way you spin it but I like to at least do it with RSpec.

Why Test the Internals?

Lets say you have a method that does some expensive lookup:

class Library < ActiveRecord::Base
  include ExpensiveQueries

  att_accessor :books
  def books
    @books ||= expensive_query # The expensive query takes 5 seconds
  end
end

The above example should be familiar, you plan to perform something Ruby or database expensive and you would like to cache the result in an instance variable so that all subsequent calls to that method draw from the instance variable.

If you were taking a TDD approach, you wouldn't have this class already written. In that case, you know you'll be performing something very expensive and you want to ensure that method caches the result. How do you test this without knowing the internals of the method?

Stubbing with RSpec

Let's say you're writing your tests before you write the above class. You could use RSpec's stubbing library to ensure your method is caching it's result.

describe Library do
  describe '#books' do
    it 'caches the result' do
# Assume some books get associated upon creation
      @library = Library.create!

# 5 seconds for this call
      the_books = @library.books

# Stub out the expensive_query method so it raises an error
      @library.stub(:expensive_query) { raise 'Should not execute' }

# If the value was cached, expensive_query shouldn't be called
      lambda { @library.books }.should_not raise_error
    end
  end
end

The key component here is the @library.stub call. This is also where we're breaking the black box, behavior driven test idiom. We assume at this line that we know there will be a method call internally named expensive_query. This test is also brittle because if expensive_query ever changes it's name to really_expensive_query, our test will break even though the functionality of our method remains the same.

Stubbing Rails Associations

What if your expensive_query is really an ActiveRecord association? So, let's say your Library class looks more like the following:

class Library < ActiveRecord::Base
  has_many books

  att_accessor :authors
  def authors
    @authors ||= books.authors # The expensive query takes 5 seconds
  end
end

You could use the nifty stub_chain method provided by RSpec to stub the books.authors method and ensure it only gets called once.

describe Library do
  describe '#books' do
    it 'caches the result' do
# Assume some books and authors get associated upon creation
      @library = Library.create!

# 5 seconds for this call
      the_authors = @library.authors

# Stub out the books.authors association so it raises an error
      @library.stub_chain(:books, :authors) { raise 'Should not execute' }

# If the value was cached, books.authors shouldn't be called
      lambda { @library.authors }.should_not raise_error
    end
  end
end

Arguments to stub_chain represent the associations used. stub_chain could also be used to stub out additional methods which get called within the chain.

Happy stubbing!

Posted by Mike Pack on 10/07/2011 at 11:20AM

Tags: rails, rspec, stubbing


Testing subdomains with RSpec and Capybara

There's a lot of ugly solutions out there regarding this problem and rightfully so, it's a pain in the ass. I've gone through a good number of options and found the following to be the most simple. Warning: It only works on Mac OS X.

Configure Capybara

Capybara allows you to set the server port to run the test server on. This may or may not be necessary depending on your environment. It was necessary for me because Pow hijacks my default port.

spec_helper.rb

Capybara.server_port = 6543

Use the Subdomain

Mac OS X comes with a handy *.127localhost.com domain configured for you. So you can do contact.127localhost.com, test.127localhost.com, etc and it will all point to 127.0.0.1.

When you need to use your subdomain, simply use a url helper:

login_spec.rb

describe 'as a guest user on the mobile login page' do
  before do
    visit login_url(:subdomain => 'contact', :host => '127localhost.com', :port => 6543)
  end

  it 'shows me the login form' do
    page.should have_content('Login')
  end
end

Note: The :subdomain option isn't available by default. I stole it from Ryan Bate's Railscast and gisted it.

Other Options

Fenangling the /etc/hosts feels dirty and pollutes domain resolution.

Ultimately, it would be awesome to boot a Pow server for testing and use something like http://contact.myapp.test where the development app lives at http://contact.myapp.dev. It's possible to get Pow to boot at http://myapp.test by using the POW_DOMAINS environment variable. The issue here is the Pow server still runs in development. You can start the Pow server in an environment other than development by using .powenv. The issue here is managing the .powenv file so that it only exists during testing. Also, there would be additional boot time by restarting the Pow server.

I hope that in future versions of Pow, it's possible to configure the environment based on the top-level domain such as dev or test. That way, you can modify /etc/resolver/test to point to the port for which your test app lives during runtime. The resolver files are pretty simple and could be modified to look like the following:

/etc/resolver/test

# Lovingly generated by Pow
nameserver 127.0.0.1
port 6543

Until Pow provides the ability to run different environments, I'll stick to *.127localhost.com.

Happy testing!

Posted by Mike Pack on 09/16/2011 at 11:01AM

Tags: subdomains, capybara, testing, rspec, pow


Testing Mobile Rails Apps with Capybara

Every web app should have a mobile version and every mobile version should be tested. Testing mobile web apps shouldn't be any more painful than testing desktop apps with the assumption that you're still serving up HTML, CSS, and JavaScript.

My Setup

For mobile detection, I use ActiveDevice, a User Agent sniffing library and some helper methods. While User Agent sniffing isn't the best approach for client-side (use feature detection with something like Modernizr), it's a reliable way to detect mobile devices in Rails.

For acceptance testing that doesn't need to be readily demonstrated to stakeholders, I use straight up Capybara with RSpec. Sometimes I use Steak.

The Pain of Testing Mobile

It's difficult to test mobile web apps because Capybara's default drivers are all desktop User Agents. You could acquire Capybara-iphone, but this solution didn't produce the expected results for me. I was given my mobile views but not my mobile layout. Plus, all this driver does is reset the User Agent for the Rack-Test driver. Further, what if you use Selenium for your default driver?

Platformatec wrote a nice blog post about mobile testing with user agents. The problem is it relies on Selenium. I was seeking a more concrete, driver agnostic way to serve up mobile views to my tests.

How I Test Mobile

This is a simple, straightforward way to invoke your mobile app from within your tests. I'm not convinced it's the most elegant way, but it's clean and simple.

Setup Your Application

In your ApplicationController, give some support for changing over to  your mobile app. This will also come in handy when you want to work on and test your mobile app on your desktop browser.

app/controllers/application_controller.rb

@@format = :html
cattr_accessor :format

Here we simply add a class attribute accessor with a default of :html. This will allow us to say ApplicationController.format

Next we need to add a before filter which will set the desired format upon each request.

app/controllers/application_controller.rb

before_filter :establish_format

private
def establish_format
  # If the request is from a true mobile device, don't set the format
request.format = self.format unless request.format == :mobile
end

Here's what a sample Rails 3 ApplicationController would look like:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery
  include ActiveDevice

  # force the mobile version for development:
  #@@format = :mobile
  @@format = :html
  cattr_accessor :format
  before_filter :establish_format

private
  def establish_format
# If the request is from a true mobile device, don't set the format
request.format = self.format unless request.format == :mobile
end
end

Setup Your Tests

Now, in your tests, you can set the desired format for your application.

spec/integration/mobile/some_spec.rb

require 'spec_helper'

describe 'on a mobile device' do
  before do
    ApplicationController.format = :mobile
  end

  after do
    ApplicationController.format = :html
  end

  describe 'as a guest on the home page' do
    before do
      visit root_path
    end

    it 'does what I want' do
      page.should do_what_i_want
    end
  end
end

By setting ApplicationController.format = :mobile, we force the application to render the mobile version of files, for instance: index.mobile.erb. Your application will be invoked, your before filter will be run, and your are serving the mobile app to your tests.

Note: You need to reset your format to :html after your mobile tests are run so that tests which follow this in your suite are run under the default format, :html.

Happy testing!

Posted by Mike Pack on 06/16/2011 at 11:05AM

Tags: rails, testing, rspec, capybara, mobile