【问题标题】:Testing and mocking the contents of a block with RSpec使用 RSpec 测试和模拟块的内容
【发布时间】:2015-06-11 10:09:52
【问题描述】:

我正在为我的一个服务对象编写单元测试。在这种特殊情况下,我需要使用事务来确保数据完整性。因此,我有一个像这样的简单代码:

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。但是,这个问题的重点实际上是如何测试这段代码。我已经成功地使用了 mocks 和 stubs,但这是我第一次必须使用涉及块的东西来存根。

起初我有一个像这样的幼稚规范:

    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)

它会将事务块的内容存根,但它仍然会失败。

如何在我的规范中设定期望:

  • 期望交易被使用
  • 期望创建消息发送到 user_klass
  • 期待 find_or_create 消息发送到 another_model_klass

【问题讨论】:

    标签: ruby rspec rspec-rails sequel rspec3


    【解决方案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,因为它们提供了一些非常好的保证,而普通替身无法提供。

    【讨论】:

    • 我真的很挣扎“我可以根据经验说,在模拟你不拥有的 API 的道路上存在脆弱、低价值的测试和很多痛苦。”我不想编写使用第三方 API 的测试。我想模拟这些 API 的结果来测试我编写的代码路径。我这样做是为了隔离我的代码。
    【解决方案2】:

    看看间谍https://github.com/rspec/rspec-mocks#test-spies,你也许可以在双打中丢掉他们。 :-)

    【讨论】:

    • 对 -1 的评论是合适的。承担责任,让作者用他/她的回答来理解你的问题。
    猜你喜欢
    • 2014-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多