Reputation: 65517
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
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:
Mock Object:
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