Read my latest article: 8 things I look for in a Ruby on Rails app (posted Thu, 06 Jul 2017 17:59:00 GMT)

Spec Your Views

Posted by Thu, 02 Aug 2007 07:00:00 GMT

I meant to work on this post… oh about 7 months ago.

Way back in January (7 months ago), Jamis Buck posted an article titled, Testing your views, which gave a few tips on using Test::Unit to, as the title suggests, test your views.

While, I’m not going to rewrite everything that Jamis wrote, I’d like to show you how to test these views with RSpec. (you might take a moment to quickly read his post…)

In this example, I’m going to show you how we’re able to write specs for the following RHTML, which you’ll notice matches the code that he wrote tests for.


  <% if @user.administrator? %>
    Hi <%= @user.name %>! You appear to be an administrator.
    <%= link_to "Click here", admin_url, :id => "admin_link" %>
    to see the admin stuff!
  <% end %> 

Jamis writes, “The only really significant thing you ought to be testing here is that the admin link only shows up for administrators. “

So, let’s do just that, but with RSpec.

I’m not sure how Jamis is handling his view tests, but we’re going to approach our view specs, much like we approach our controller specs, with the use of mocks and stubs, because we really don’t need to spec any of our models at this level in the application.

Tip: Write specifications for your models… in your model specs not in your controller or view specs.

The first thing that we’re going to do is setup a custom spec helper, because for something like an mocked user, will probably get reused in other areas of the user interface. Spec helpers are essentially modules that you can include in your RSpec descriptions (the block that starts with describe) and reuse.

In this spec helper, I’m going to include two methods, to mock the User model and stub out any of the methods that are necessary for spec’n this view.


module MockUserHelper
  def mock_normal_user
    user = mock(User)
    user.stub!(:administrator?).and_return(false)   # <--- NOT an admin
    user.stub!(:name).and_return('David Chelimsky')
    return user
  end

  def mock_admin_user
    user = mock(User)
    user.stub!(:administrator?).and_return(true)    # <--- IS an admin
    user.stub!(:name).and_return('Aslak Hellesoy')
    return user
  end
end

In the mock_normal_user method, we’re constructing a mock object and stubbing out the methods that we see are being called in the RHTML code. In mock_admin_user, we’re basically doing the same thing, but just stubbing the administrator? method to return true for this mock user.

By stubbing these methods, we’ll be able to send a non-ActiveRecord object to the view and have it render without knowing the difference. For example, the if @user.administrator? condition will return true or false, depending on how we stubbed it.

For more information on mocks and stubs, read here.

Now that we have our spec helper, let’s go ahead and dive into a few specifications for the view.


describe "index page" do
  include MockUserHelper

  it "should render an admin link for an admin user" do
    assigns[:user] = mock_admin_user
    render 'index'
    response.should have_tag('a#admin_link')
  end

  it "should not render an admin link for a normal, non-admin user" do
    assigns[:user] = mock_normal_user
    render 'index'
    response.should_not have_tag('a#admin_link')
  end
end  

Please note: This code example is only longer than the one shown by Jamis because he didn’t include how he setup all his user sessions/objects. ;-)

When these specs are run, we can see the following results.


Pretty output courtesy of RSpec + TextMate bundle

Great, we’ve been able to write specifications for our Rails views without a lot of pain. Stay tuned for more posts on this topic as I continue writing about how Designers and Developers can work together, in harmony. (see my last post on this topic)

For more information on adopting RSpec, please visit the RSpec project homepage.

Be Careful that you don't Stub your Big Toe

Posted by Tue, 13 Feb 2007 07:09:00 GMT

In a project that I’m currently working on, we’re handling recurring payments for subscribers. I’ve decided to play with a different payment service API on this project (TrustCommerce), which supposedly has one of the easier systems to handle recurring payments as well as one-time charges to the same credit cards. They store all the credit card data so that our delivered product to the client is CISP-compliant.

I came across the TrustCommerce Subscription plugin for Rails, which does just everything that I need to do in this first product release… as well as things that aren’t requirements just yet.

Well, I got my test account from TrustCommerce and was working on some RSpecs to test my new subscription and noticed that it was failing. After some snooping around the error responses, I realized that… test accounts don’t give you the ability to test the Citadel features of TrustCommerce. It’ll be another week or so before finish getting our account setup, so what am I to do? I really want to finish writing these specs and move on to the other portions that are dependent upon this working.

Suppose that you were going to perform something like this in an AR callback.


class BillingDetail < ActiveRecord::Base

  # validations    

  before_create :store_credit_card_data_with_trust_commerce

  private 

    def store_credit_card_data_with_trust_commerce
      # some of this is still test data... prettyu much copied from the README
      # TODO: refactor... but keep me out of controllers!
      response = TrustCommerceGateway::Subscription.create(
          :cc =>  self.credit_card_number, 
          :exp => '0412', 
          :name => self.customer_name,
          :amount => 1,
          :cycle => '1y',
          :demo => 'y'
        )

      if response['status'] == 'approved'
          self.billing_id = response['billingid']
        else
        # handle failure
        end
    end
end

Enter Mock Objects

Since I am unable to succesfully use the TrustCommerceGateway::Subscription.create method until I get our real account, I needed a simple way to emulate the interaction with the web service.

This can be done by using a Mock object, which RSpec provides for you.


TrustCommerceGateway::Subscription.stub!(:create).and_return( {expected response} )

Let’s look at the following spec file (much of it removed to protect the innocent).


module ValidBillingDetail
  def valid_attributes
    { # a hash of valid key/values for this model }
  end

  def approved_trust_commerce_subscription
    { 'status' => 'approved', 'billingid' => '1093423' } 
  end
end

context "A new billing detail" do
  include ValidBillingDetail

  setup do
    TrustCommerceGateway::Subscription.stub!(:create).and_return( approved_trust_commerce_subscription )
  end

  # bunch of other specs

  specify "should store new billing info with 3rd party API and store the billingid" do
    @billing_detail = BillingDetail.create( valid_attributes )
    @billing_detail.billing_id.should_not_be nil
  end
end  

You’ll notice a few things. First, you’ll see that I’ve stubbed the create method and when it is called in the method in my model, it’ll return the hash that I’ve specified.

TrustCommerceGateway::Subscription.stub!(:create).and_return( approved_trust_commerce_subscription )

In the spec, you will see that I am checking that that the .billing_id.should_not_be nil. If you look back in the method in the model above, you will notice that an approved subscription returns a billing_id, which is set when the transaction is successful.

This is working out great for me and because the documentation is fairly easy to follow, I’m going to be able to mock much of the behavior that I’ll be using in the application, without needing to even connect to their API.

If you’re using RSpec, I highly encourage you to read more about mocks objects.