我正在为我的一个服务对象编写单元测试。在这种特殊情况下,我需要使用事务来确保数据的完整性。因此,我有一个简单的代码:
class CreateUser
def save
user_klass.db.transaction do
user = user_klass.create(name: name, email: email)
another_model_klass.find_or_create(user_id: user.id, foo: 'foo')
end
end
end
我正在使用Sequel作为我的ORM。但是,这个问题的重点实际上是如何测试这段代码。我已经成功地使用了模拟和存根,但这是我第一次使用一个块来存根。
起初我有一个天真的规范:
describe CreateUser do
describe "#save" do
let(:user) { instance_double("User", id: 1) }
let(:user_klass) { double("Class:User", create: user) }
let(:another_model_klass) { double("Class:AnotherModel") }
let(:name) { 'Test User' }
let(:email) { 'test@test.com' }
let(:foo) { 'foo' }
let(:params) { { name: name, email: email, foo: foo } }
let!(:form) { CreateUser.new(params, user_klass, another_model_klass) }
before do
allow(another_model_klass).to receive(:find_or_create)
end
it "sends create message to the user_klass" do
expect(user_klass).to receive(:create).with({ name: name, email: email}).and_return(user)
form.save
end
it "sends find_or_create message to another_model_klass" do
expect(another_model_klass).to receive(:find_or_create).with(user_id: user.id, foo: foo)
form.save
end
end
end
这会发出错误:
Double "Class:User" received unexpected message :db with (no args)
但如果我添加以下内容:
allow(user_klass).to receive_message_chain(:db, :transaction)
它将存根事务块的内容,它仍然会失败。
如何在我的规范中设定期望值:
答案 0 :(得分:1)
你可以这样做:
let(:db) { double("DB") }
let(:user_klass) { double("Class:User", create: user, db: db) }
# ...
before do
allow(db).to receive(:transaction).and_yield
# ...
end
那说:你可以这样做,但我建议你不要这样做。事实上,我建议你根本不要嘲笑Sequel API。我可以从经验中说出,在嘲笑API的过程中,你不会有谎言脆弱,低价值的测试和许多痛苦。我(和许多其他人)推荐的一般方法是包装您不拥有自己拥有的API的API。然后,您可以集成测试您的包装器(无需进行模拟或存根),并在依赖于该功能的所有其他位置模拟或存储您更简单的特定于域的API。
另外,如果您使用的是RSpec 3,我强烈建议您将测试双打切换到verifying doubles,因为它们提供了一些非常好的保证,即正常的双打不能提供。< / p>
答案 1 :(得分:-1)
看一下间谍https://github.com/rspec/rspec-mocks#test-spies,你可以把它们放在你的双打中。 : - )