Blog

Showing posts with label rspec. Show all posts
Showing posts with label rspec. Show all posts

Friday, March 9, 2012

Testing Terminology: Spies, Stubs and Mocks

When referring to the definitions of Spies, Stubs and Mocks I like to return to XUnit Test Patterns:

  • Test Spy: "We use a Test Double to capture the indirect output calls made to another component by the SUT for later verification by the test."
  • Test Stub: "We replace a real object with a test-specific object that feeds the desired indirect inputs into the SUT."
  • Mock Object: "We replace an object the SUT depends on with a test-specific object that verifies it is being used correctly by the SUT."

You may have noticed that a Spy and Mock seem to have similar definitions, and do serve similar purposes, however Mocks specify expectations up-front and don't require assertions in contrast to Spies, that verify after the fact. Here's an example that compares Rspec Mocks with Bourne's Spies:

SUT is an abbreviation for System Under Test.


The above patterns are collectively know as Test Doubles: "We replace a component on which the SUT depends with a test-specific equivalent." Rspec's built-in mocking library includes a double method/alias, see An Example using RSpec double, mock, and stub.

Double-Ruby (RR) is another example of a test double framework implemented in Ruby.

Wednesday, December 21, 2011

An Example using RSpec double, mock, and stub

This is a follow-up post for RSpec double, mock, and stub from earlier this year. A reader asked for an example code to illustrate the various double aliases in RSpec.

For this example we will spec a Transfer class that encapsulates the use case of transferring an amount between two accounts, the source account and destination account. The Transfer is the subject of our examples, while the source and destination accounts are the collaborators.

In the spec below, the first example "should decrease source amount by 10", specifies the interaction for the source account, hence the use of the mock to generate the test double. In this example, the destination account is treated as a secondary collaborator, as we are focusing on the role of the source account. Any object can play the role of the source account, as long as it responds to the #decrease method.

In the second example "should increase destination account by 10", the destination account is the primary collaborator. The use of mock to generate the source account communicates this intent.

describe Transfer do
  context "transfer with amount of 10" do
    it "should decrease source account by 10" do
      source_account      = mock('source account')
      destination_account = stub('destination_account').as_null_object
    
      source_account.should_receive('decrease').with(10)
    
      transfer = Transfer.new(source_account, destination_account, 10)
      transfer.call
    end

    it "should increase destination account by 10" do
      source_account      = stub('source account').as_null_object
      destination_account = mock('destination_account')
    
      destination_account.should_receive('increase').with(10)
    
      transfer = Transfer.new(source_account, destination_account, 10)
      transfer.call
    end
  end
end

However, there is some duplication in this spec which can be eliminated by moving the test doubles to let blocks. It's appropriate to use double here to generate the source and destination accounts as both play primary and secondary collaborators in the following examples. Below we stub the methods for each collaborator, then set expectations on those methods for the appropriate examples.

describe Transfer do
  context "transfer with amount of 10" do
    let(:source_account) { double('source account', :decrease => nil) }
    let(:destination_account) { double('destination_account', :increase => nil) }

    it "should decrease source account by 10" do
      source_account.should_receive('decrease').with(10)
    
      transfer = Transfer.new(source_account, destination_account, 10)
      transfer.call
    end

    it "should increase destination account by 10" do
      destination_account.should_receive('increase').with(10)
    
      transfer = Transfer.new(source_account, destination_account, 10)
      transfer.call
    end
  end
end

Update: Radoslav Stankov provides a cleaner refactoring of the spec.

The implementation of the transfer class may look something like this:

class Transfer
  def initialize(source_account, destination_account, amount)
    @source_account      = source_account
    @destination_account = destination_account
    @amount              = amount
  end
  
  def call
    # In a complete implementation, it would make sense to have the following 
    # lines wrapped in a transaction, but I wanted to keep this example simple
    #
    @source_account.decrease(amount)
    @destination_account.increase(amount)
  end
end

It's important to remember that double, mock, and stub all return an instance of RSpec::Mocks::Mock. While the concepts of mocking, (i.e. setting a method expectation), and stubbing are "method-level concepts"[1], the use of mock and stub can communicate which collaborator you are specifying the behaviour for.

[1] For a more complete discussion on this, checkout The RSpec Book.

Saturday, July 30, 2011

Gotcha with testing expected params with Bourne/Mocha in Rails Controller

This is something that always trips me up using Bourne (a fork of Mocha, a Ruby mocking and stubbing library) to test expected params in a Rails Controller. Take this spec:

context "with filter" do
  before do
    get :index, :search => {:brand_id => '1'}
  end

  it 'should find latest search results' do
    SearchResult.should have_received(:latest).with('brand_id' => '1')
  end
end

This test fails, but what if I tried this:

context "with filter" do
  before do
    get :index, :search => {:brand_id => '1'}
  end

  it 'should find latest search results' do
    SearchResult.should have_received(:latest).with(:brand_id => '1')
  end
end

This will fail. Note I am now checking the parameters passed using symbols for the keys. I receive the following error:


Failure/Error: SearchResult.should have_received(:latest).with(has_entry(:brand_id => '1'))
     expected exactly once, not yet invoked: SearchResult(id: integer, search_id: integer, body: text, source: text, url: string, created_at: datetime, updated_at: datetime).latest(has_entry(:brand_id => '1'))

This is because the params that are passed to the controller have keys as strings rather than symbols:

{'brand_id' => '1'}

So what's the solution? I could actually use HashWithIndifferentAccess which gives access to a Hash using strings or symbols for keys. This is in fact the class that is used for the params hash in the controller:

it 'should find latest search results' do
    SearchResult.should have_received(:latest).with(HashWithIndifferentAccess.new(:brand_id => '1'))
  end

Or, as a standard practice, when dealing with parameters passed to a controller within a spec, always use strings:

context "with filter" do
  before do
    get :index, 'search' => {'brand_id' => '1'}
  end

  it 'should find latest search results' do
    SearchResult.should have_received(:latest).with('brand_id' => '1')
  end
end

I think this is a lot simpler, cleaner. Interestingly, I didn't pass the brand_id as a Fixnum:

before do
    get :index, 'search' => {'brand_id' => 1}
  end

I would have got the same error:

Failure/Error: SearchResult.should have_received(:latest).with('brand_id' => 1)
     expected exactly once, not yet invoked: SearchResult(id: integer, search_id: integer, body: text, source: text, url: string, created_at: datetime, updated_at: datetime).latest('brand_id' => 1)

As the brand_id is present in the hash as a String, not a Fixnum.

Sunday, February 6, 2011

4 Things I Assert in Rails Controller Tests

  1. ActiveRecord Calls (using mocking)
  2. Assigns
  3. Flash
  4. Responses

And if the response content type is not going to be HTML I will test that as well.

Friday, January 14, 2011

Stub called with unexpected arguments fixed in RSpec2 Mocks

In RSpec 1.3 the following would pass:

describe Person do
 describe "#greeting" do
   context "using #stub with correct parameters" do
     before(:each) do
       subject.stub(:greeting).with("Nicholas")
     end

     specify { lambda{ subject.greeting("Nicholas") }.should_not
raise_error(NoMethodError) }
   end

   context "using #stub with incorrect parameters" do
     before(:each) do
       subject.stub(:greeting).with("incorrect parameter")
     end
     specify { lambda{ subject.greeting("Nicholas") }.should
raise_error(NoMethodError) }
   end
 end
end

From the example above you can see that calling a stuff with a different parameter would raise a NoMethodError. A bit confusing.

Fortunately in RSpec 2 it fixed. The following error is raised:

1) Person#greeting using #stub with incorrect parameters 
     Failure/Error: specify { lambda{ subject.greeting("Nicholas") }.should raise_error(NoMethodError) }
       expected NoMethodError, got ##<RSpec::Mocks::MockExpectationError: #<Person:0x0000010216fb68#> received :greeting with unexpected arguments
         expected: ("incorrect parameter")
              got: ("Nicholas")>

Related links:

  • My post to RSpec mailing list
  • http://github.com/rspec/rspec-mocks/issues/20
  • http://groups.google.com/group/rspec/browse_thread/thread/ab3519f98597815c

RSpec double, mock, and stub

RSpec uses double to create a test double. However, there are two aliases for this method mock and stub. When do we use which alias? It depends on what role the test double is fulfilling.

If the test double is the focus of the test, then you will be setting a message expectation on the double, which takes the "mock" role.

If the test double is not the focus, and you are simply stubbing out methods on an object, then this takes the "stub" role. You can often eliminate the number of method stubs by ignoring method calls using #as_null_object.

Thursday, December 30, 2010

Specifying Behaviour vs Specifying Implementation

At the JRubyConf in Columbus, Ohio last October, Jim Weirich presented "Testing – Why Don't We Do It Like This?". At the end of the presentation he suggested asking two questions to ensure our tests are specifying behaviour rather than implementation:

  1. If I wanted to use this software in my project, what behaviours are important to me?
  2. Could I write this software from scratch using only the tests/specs as guidance?

These are definitely useful questions to ask, rather than blindly writing specs to simply get 100% test coverage. Check the entire presentation below:

Testing – Why Don't We Do It Like This? – Jim Weirich from Engine Yard on Vimeo.

Sunday, December 5, 2010

The Mocking Debate Continues

Nick Gauthier recently blogged about "Everything that is wrong with mocking, bdd, and rspec". Nicks' a smart guy, you just need to check out his presentations to understand that. In his post he attempts to demonstrate the brittleness of mocking. While his arguments appear sound at first glance, Pat Maddox presents a well articulated argument. Reading his points on collaborators and protocols gave me a clearer understanding of BDD. I repeat here, but encourage you to check out the comments check out the comments. It's a great debate.

The mock-based example specifies the interaction with the collaborator. It demonstrates that you can pass in any object that responds to #puts(str) and expect it to work.

Your refactoring is valid -- when the output object responds to #puts, #write, #print, and #<<, and implements them all in basically the same way.

You are free to change the internals of a method however you want, but remember that certain changes will have broader-reaching effects than others. Changing how you build up the string to, say, an array of strings that then get joined, should certainly be encapsulated. Changing how you call collaborators is a totally different thing -- you're changing the contract, potentially in a way that's incompatible with existing clients.

Focused examples (or unit tests, or microtests, or whatever) help answer the question, "what do I need to know to use this code?" When there is a collaborator involved, one of the key things to know is the protocol that the collaborator must adhere to. An interaction-based example makes that protocol explicit. Moreover, it establishes a checkpoint. Should you decide to call #write instead of #puts, you have to consider the possible implications to existing code.

I understand where you're coming from. When you run the program, there is no externally visible difference in behavior. That is why we tend to separate the duties of checking program behavior into acceptance tests. Your unit tests then become a place for you to check an object's correctness, which includes its own logic and its interactions with other objects. Don't assume that because the external behavior of the program stays the same, that the behavior internal to objects has stayed the same as well.

Thursday, October 14, 2010

Slow startup with Rspec (1.3.0) and Spork

This is probably network specific, because at my home office things are fine, but on site at a client, Rspec starts up very slow when running against spork. I tracked it down to line 19 in $GEM_PATH/gems/rspec-1.3.0/lib/spec/runner/drb_command_line.rb

DRb.start_service("druby://:0"); \

Change it to...

DRb.start_service("druby://127.0.0.1:0"); \

I think I have seen postings to the Rspec mailing list in regards to this issue. I will try to track them down to add to this post.

Update

It appears this is an OS X related issue. Checkout this thread for more information: http://www.ruby-forum.com/topic/154556. Thank you, Stuart.

Friday, September 17, 2010

The Other RSpec Methods: #subject, #specify, and #let

Recently I switched to RSpec from Shoulda. This switch was driven mostly by this announcement explaining ThoughtBot's intention in providing matchers for RSpec, rather than a context framework. Here are three highlights from my experience using RSpec over the last month. I call them the other RSpec methods, as their value becomes apparent after using RSpec for a while. I view their purpose as cleaning up setup code located in before blocks.

#let

Get rid of instance variables using #let. Let memoizes a block:

# before "#let"
  before do
    @valid_customer = Customer.new(:name => "Frank Reynolds")
  end

  it "should be valid" { @valid_customer.should be_valid }

  # with "#let"
  let(:valid_customer) { Customer.new(:name => "Frank Reynolds") } 

  it "should be valid" { valid_customer.should be_valid }

#subject and #specify

The subject specifies the object's behaviour that's being described:

subject { Customer.new(:name => "Frank Reynolds") }

 it "should be valid" { subject.should be_valid }

And it doesn't make sense to describe the behaviour ourselves, since RSpec will generate that for us:

subject { Customer.new(:name => "Frank Reynolds") }

 it { subject.should be_valid }

That doesn't read well, so let's use the alias "#specify":

subject { Customer.new(:name => "Frank Reynolds") }

 specify { subject.should be_valid }

But we can use an implicit subject, meaning we don't need to specify the subject (and we revert back to #it):

subject { Customer.new(:name => "Frank Reynolds") }

 it { should be_valid }

Finally, we can also use an implicit subject based on the object we are describing:

describe Customer do
  it { should_not be_valid }
 end

What are your "other" RSpec methods? Let me know in the comments.

Friday, September 10, 2010

Autospec problem, autospec exiting without results

A little frustration today with autospec (to clarify, not autospec's fault). I had recently installed RVM and started using gemsets (really this is amazing). When setting a default gemset I installed a bunch of gems including Paperclip 2.3.3. Unfortunately, Paperclip's dependencies include ActiveSupport and installed version 3.0.0. My current default gemspec is setup for rails 2.3.8, I have built a separate one for Rails 3.0.0 on Ruby 1.9.2. For some reason, with ActiveSupport 3.0.0 installed, caused autospec to exit without any results.

(Not running features.  To run features in autotest, set AUTOFEATURE=true.)
loading autotest/rails_rspec
style: RailsRspec

The solution, simply uninstall ActiveSupport 3.0.0 (and any other 3.0.0 Rails dependencies) Paperclip 2.3.3 runs just fine with 2.3.8. This really makes sense since I want to keep a clean 2.3.x environment. To be clear, I had not installed Rails 3.0.0 in this gemset.

Thursday, September 9, 2010

RSpec #stub vs. #stub!

Confused by #stub vs. #stub!? Consider #stub! deprecated and use #stub. #stub is simply an alias for #stub!. #stub! was defined in the API at a time when RSpec's authors weren't clear on the intention of "bang" methods in Ruby. Quoting David Chelimsky's email on RubyForge:

The original method was stub!, which I will confess to introducing, and for the wrong reasons, due to my misunderstanding of the meaning of ! in ruby methods. I recently added stub (with no !) with no particular fanfare, as I don't intend to remove stub! (at least not any time soon). Right now the docs don't make it clear, so I need to fix that - but I'd say that stub() is the method to use and stub!() is there for backwards compatibility.

The confusion is compound in The RSpec Book (B15.0 printing, July 29, 2010) where there is as single reference to #stub! on page 199.

References:

Please note this blog is no longer maintained. Please visit CivilCode Inc - Custom Software Development.