Zabba
Zabba

Reputation: 65517

How to use mocks correctly?

I have this class:

class EnablePost

  def initialize(post_klass, id)
    raise "oops" if post_klass.blank?
    @post_klass = post_klass 
    @id = id
  end

  def perform
    post = @post_klass.find_by_id(@id)
    return unless post
    post.update_attribute :enabled, true
  end

end

The spec I have to write to test the above:

describe EnablePost do
  it "should enable a post" do
    post = mock
    post.should_receive(:blank?).and_return(false)
    post.should_receive(:find_by_id).with(22).and_return(post)
    post.should_receive(:update_attribute).with(:enabled, true)
    result = EnablePost.new(Post, 22).perform
    result.should be_true
  end
end

But what I really want to do is treat EnablePost as a black box. I don't want to have to mock :blank?, :find_by_id or :update_attribute. That is to say I want my spec to look like:

describe EnablePost do
  it "should enable a post" do
    post = mock
    result = EnablePost.new(post, 22).perform
    result.should be_true
  end
end

What am I missing here? Am I using mocks incorrectly?

Upvotes: 3

Views: 149

Answers (1)

joelparkerhenderson
joelparkerhenderson

Reputation: 35493

Yes, you're confusing mocks and stubs.

A good mock explanation: http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-at-lrug/

Mocks:

  • Different things to different people
  • Ambiguous terminology
  • Confusion with Rails “mocks”

Mock Object:

  • Expected method invocations set in advance
  • Verifies actual invocations match expected ones

Also check out http://martinfowler.com/articles/mocksArentStubs.html [thanks to user Zombies in the comments]

If you're using RSpec, it aliases double, mock, and stub. RSpec expects you to choose whichever method name makes your code clearest.

Your first chunk of test code is using the word "mock" correctly. You're setting up the method invocations that you expect to be called, in advance, and then performing them.

However, you're testing two different areas of your code: the first area is the initialize method, the second is the #perform method.

You may find it easier to mock and stub if you write smaller methods:

# What you want to test here is the raise and the member variables.
# You will stub the post_klass.
def initialize(post_klass, post_id)  # post_id is a better name
  raise "oops" if post_klass.blank?
  @post_klass = post_klass 
  @post_id = post_id  # because we don't want to mask Object#id
end

attr_accessor :post_id  
attr_accessor :post_klass

# What you want to test here is the post_klass calls #find_by_id with post_id.
# See we've changed from using instance variables to methods.
def post
  post_klass.find_by_id(post_id)
end

# What you want to test here is if the update happens.
# To test this, stub the #post method.
def perform
  p = post
  return unless p
  p.update_attribute :enabled, true
end

When you write your code this way, you make it easy to stub the #post method.

See this for RSpec example source code showing the difference between mock and stub:

http://blog.firsthand.ca/2011/12/example-using-rspec-double-mock-and.html

Upvotes: 2

Related Questions