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 check 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 I really want to do it EnablePost

like a black box. I don't want to mock :blank?

, :find_by_id

or :update_attribute

. That is, I want my spec to look like this:

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?

+3


source to share


1 answer


Yes, you are confusing ridicule and stubs.

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

Mocks:

  • Different things for different people
  • Ambiguous terminology
  • Confusion with Rails "mocks"

Object layout:

  • Expected method calls set in advance
  • Checks for valid calls that match expected

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

If you are using RSpec, these are aliases double, mock and stub. RSpec expects you to choose any method name that makes your code as clear as possible.



Your first piece of test code is using the word "mock" correctly. You set up the method calls you expect to be called in advance, and then you make them.

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

You may find it easier to cheat 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 can easily stub the #post method.

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

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

+2


source







All Articles