【问题标题】:Mocking Rspec any_instance_of method return value relative to instance's actual args相对于实例的实际参数模拟 Rspec any_instance_of 方法返回值
【发布时间】:2017-11-15 23:15:38
【问题描述】:

我遇到了一个问题,我试图模拟服务类中的异步方法,但我希望结果集基于调用的输入。

例如:

allow_any_instance_of(Emails::EmailService)
  .to receive(:send_many_async)
  .and_wrap_original { |m, *args| args[1].map { |recipient| Emails::EmailResult.new(recipient.email) } }

这个方法的签名是send_many_async(email, users)

我不认为and_wrap_original 的行为与我预期的一样,当我打印出margs 的值时,我看到了:

#<Method: Emails::EmailService#__send_many_async_without_any_instance__(send_many_async)>
#<Emails::EmailService:0x007fa5c4e1b448>
#<Emails::MyEmail:0x007fa5c4e1b4e8>

如,我希望arg[0]email 对象,arg[1] 是用户列表——而不是arg[0] 是我正在模拟的服务,并且没有用户列表完全没有(例如:arg[2]

我实际上只是想确认调用了该方法,并且用户是我希望他们成为的人数。

或者,如果我可以将同一类上的另一个方法替换为另一个也可以工作的方法(非异步调用),但我无法找到将:send_many_async 替换为常规:send_many 的方法以另一种方式成功模拟(我不能在这里申请)

有人成功地做这样的事情吗?我期待 and_wrap_original 提供传递给实例方法的内容,但显然我在这方面感到困惑。

编辑

如果有人想知道,这里有一个有效的测试样本。不幸的是,它在我的应用程序中还没有为我工作,但其他人可能会觉得它有帮助。

class TestEmailSvc
  def send_many_async(email, to_email_users, from_email_user: nil, send_at: nil)
    puts 'Real one called!'
  end
end

class TestEmail
end

class TestEmailUser
  attr_reader :email
  def initialize(email)
    @email = email
  end
end

class TestEmailResult
  attr_reader :user
  def initialize(user)
    @user = user
  end

  def status
    :pending
  end
end

describe 'Test Spec' do

  it 'tests my mock' do
    allow_any_instance_of(TestEmailSvc)
      .to receive(:send_many_async)
        .and_wrap_original { |m, _, args| args.map { |recipient| TestEmailResult.new(recipient) } }

    svc = TestEmailSvc.new

    users = [
      TestEmailUser.new('email 1'),
      TestEmailUser.new('email 2')
    ]

    results = svc.send_many_async(TestEmail.new, users)

    expect(results.count).to eql 2
  end

end

【问题讨论】:

    标签: ruby-on-rails ruby unit-testing rspec


    【解决方案1】:

    当您使用allow_any_instance_of(...).to receive(...).and_wrap_original 时,该块会收到以下信息:

    • 原始方法
    • 收到消息的实例(如果您想使用块中实例的任何状态)
    • 调用者传递的参数,按顺序

    在您的情况下,听起来您不需要在块中使用 EmailService 实例,因此您可以忽略该参数:

    allow_any_instance_of(Emails::EmailService)
      .to receive(:send_many_async)
      .and_wrap_original { |m, _, *args| args[1].map { |recipient| Emails::EmailResult.new(recipient.email) } }
    

    另外,我鼓励您考虑是否有一种方法可以改进Emails::EmailService 的界面,这样您就不需要使用any_instance(它会遇到这些令人困惑的可用性问题,并且还倾向于钙化现有的设计,而不是给你的设计施加压力)。我们在我的书Effective Testing with RSpec 3: Build Ruby Apps with Confidence 中谈到了这一点。

    【讨论】:

    • 不幸的是,当我这样做时,args 被接收为 nil... 似乎我有一些工作要做,但拨打的电话只是 email_service.send_many_async(email, recipients) - 奇怪的是我能够创建一个按照您描述的方式工作的演示测试(我必须做的唯一更改是使用args 而不是*args)所以真的很困惑为什么它不适用于我的实际案例。
    • 块不直接接收实例。它必须调用m.receiver 才能得到它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    相关资源
    最近更新 更多