views:

247

answers:

1

I am trying to cleanup my specs as they are becoming extremely repetitive.

I have the following spec

  describe "Countries API" do 
    it "should render a country list" do
      co1 = Factory(:country)
      co2 = Factory(:country)
      result = invoke :GetCountryList, "empty_auth"

      result.should be_an_instance_of(Api::GetCountryListReply)
      result.status.should be_an_instance_of(Api::SoapStatus)
      result.status.code.should eql 0
      result.status.errors.should be_an_instance_of Array
      result.status.errors.length.should eql 0
      result.country_list.should be_an_instance_of Array
      result.country_list.first.should be_an_instance_of(Api::Country)
      result.country_list.should have(2).items
    end
    it_should_behave_like "All Web Services"
    it "should render a non-zero status for an invalid request"
  end

The block of code that checks the status will appear in all of my specs for 50-60 APIs. My first thought was to move that to a method and that refactoring certainly makes things much drier as follows :-

def status_should_be_valid(status)
    status.should be_an_instance_of(Api::SoapStatus)
    status.code.should eql 0
    status.errors.should be_an_instance_of Array
    status.errors.length.should eql 0
end

describe "Countries API" do 
    it "should render a country list" do
      co1 = Factory(:country)
      co2 = Factory(:country)
      result = invoke :GetCountryList, "empty_auth"

      result.should be_an_instance_of(Api::GetCountryListReply)
      status_should_be_valid(result.status)
      result.country_list.should be_an_instance_of Array
      result.country_list.first.should be_an_instance_of(Api::Country)
      result.country_list.should have(2).items
    end
  end

This works however I can not help feeling that this is not the "right" way to do it and I should be using shared specs, however looking at the method for defining shared specs I can not easily see how I would refactor this example to use a shared spec.

How would I do this with shared specs and without having to re-run the relatively costly block at the beginning namely

  co1 = Factory(:country)
  co2 = Factory(:country)
  result = invoke :GetCountryList, "empty_auth"
+2  A: 

Here's one option, using the new-ish "subject" feature of RSpec. Note that this will run the before :all block twice, once for each nested "describe" block. If this ends up being too slow, you can flatten things out at the cost of not being able to use the "subject" syntax for the status shared examples (since subject applies to the entire describe block it's used in).

shared_examples_for "valid status" do
  it { should be_an_instance_of(Api::SoapStatus) }
  its(:code) { should eql(0) }
  its(:errors) { should be_an_instance_of(Array) }
  its(:errors) { should be_empty }
end

describe "Countries API" do
  before :all do
    co1 = Factory(:country)
    co2 = Factory(:country)
    @result = invoke :GetCountryList, "empty_auth"
  end

  subject { @result }
  it { should be_an_instance_of(Api::GetCountryListReply) }
  its(:country_list) { should be_an_instance_of (Array) }
  it "should have countries in the country list" do
    @result.country_list.each {|c| c.should be_an_instance_of(Api::Country)}
  end
  its(:country_list) { should have(2).items }

  describe "result status" do
    subject { @result.status }
    it_should_behave_like "valid status"
  end
end
Greg Campbell
+1 That's really nice. Thanks Greg
Steve Weet
Couple of strange issues with this. Before :all throws the following error `NoMethodError in 'AdminlwController Countries API before(:all)'undefined method `recycle!' for nil:NilClass`Changing to before(:each) gets further but borks on the its(:country_list) think i'll start a new question.
Steve Weet
Yeah - obviously, I don't have access to your code (I ran this test against some entirely mocked-out objects to make sure the syntax was right, but that's it), so there may be issues I'm not aware of.
Greg Campbell
What version of RSpec are you using, by the way? The its() functionality was added in version 1.2.9.
Greg Campbell
I upgraded to the latest version (1.3.0) when I first started getting issues.
Steve Weet