我有这堂课:
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
我必须编写的规范来测试上述内容:
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
但我真正想做的是将EnablePost
视为黑盒子。我不想模仿:blank?
,:find_by_id
或:update_attribute
。
也就是说我希望我的规格看起来像:
describe EnablePost do
it "should enable a post" do
post = mock
result = EnablePost.new(post, 22).perform
result.should be_true
end
end
我在这里缺少什么?我错误地使用了模拟吗?
答案 0 :(得分:2)
是的,你混淆了模拟和存根。
一个很好的模拟解释:http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-at-lrug/
嘲笑:
模拟对象:
另请查看http://martinfowler.com/articles/mocksArentStubs.html [感谢评论中的用户Zombies]
如果您正在使用RSpec,则别名为double,mock和stub。 RSpec希望您选择使代码最清晰的方法名称。
您的第一块测试代码正确使用“mock”一词。您正在设置预期要提前调用的方法调用,然后执行它们。
但是,您正在测试代码的两个不同区域:第一个区域是initialize方法,第二个区域是#perform方法。
如果编写较小的方法,您可能会发现模拟和存根更容易:
# 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
当您以这种方式编写代码时,可以轻松存根#post方法。
请参阅此处了解RSpec示例源代码,其中显示了mock和stub之间的区别:
http://blog.firsthand.ca/2011/12/example-using-rspec-double-mock-and.html