如何在我的 rails minitest 中使用 mock?

How can I use mock in my rails minitest?

有两个 class,UserDevice(< ActiveRecord::Base) 和 NotificationAdapter。
在NotificationAdapter中,我使用AWS SDK,而UserDevice使用NotificationAdapter实例。
关节点在下方,

protected def notificationAdapter
  @notificationAdapter ||= NotificationAdapter.new(self)
end

在UserDevice测试中,我想做一个临时的NotificationAdapter mock来替换原来的NotificationAdapter,并且只在测试中使用这个mock。
但我不知道该怎么做,因为这是我在测试中使用模拟的第一个案例。

我觉得需要下面两步,

  1. 在测试代码中创建临时 NotificationAdapter class(NorificationAdapterMock)。

    NotificationAdapterMock = MiniTest::Mock.new mock.expect :setEndpoint, 'arn:testEndpoint' mock.expect :subscribeToAnnouncement, true

  2. 将UserDevice的notificationAdapter方法改成下面,

    protected def notificationAdapter @notificationAdapter ||= NotificationAdapterMock end

但不知道是对是错。我该怎么办?

你需要

  1. mock 你的 NotificationAdapter,所以它不会触发网络,而是做一些安全和简单的事情
  2. 还要确保您的适配器接线正确

旁注:请遵循 ruby style guidelines,方法名称和变量应使用蛇形而不是驼峰形式。

所以,我们可以这样写:

require 'minitest/autorun'

class NotificationAdapter
  def initialize(device)
    @device = device
  end

  def notify
    # some scary implementation, that we don't want to use in test
    raise 'Boo boo!'
  end
end

class UserDevice
  def notification_adapter
    @notification_adapter ||= NotificationAdapter.new(self)
  end

  def notify
    notification_adapter.notify
  end
end

describe UserDevice do
  it 'should use NotificationAdapter for notifications' do
    device = UserDevice.new

    # create mock adapter, that says 'ohai!' on notify
    mock_adapter = MiniTest::Mock.new
    mock_adapter.expect :notify, 'ohai!'

    # connect our mock, so next NoficationAdapter.new call will return our mock
    # and not usual implementation
    NotificationAdapter.stub :new, mock_adapter do
      device.notify.must_equal 'ohai!'
    end
  end
end

可以在 mocks and stubs 上的 MiniTest 文档中找到更多信息。

但我们不要就此打住!我建议您将业务逻辑从 ActiveRecord 模型移出到单独的服务 class。这样会有以下好的效果:

  1. 你的模特变瘦了
  2. 功能很好地封装在它自己的 class 中并且现在遵守单一职责原则
  3. 您的测试变得非常快,因为您不需要加载 Rails 并且可以模拟您的模型。

这里是:

require 'minitest/autorun'

# same adapter as above
class NotificationAdapter
  def initialize(device)
    @device = device
  end

  def notify
    raise 'Boo boo!'
  end
end

class UserDevice
# the logic has been moved out
end

class NotifiesUser

  def self.notify(device)
    adapter = NotificationAdapter.new(device)
    adapter.notify
  end
end

describe NotifiesUser do
  it 'should use NotificationAdapter for notifications' do
    # Let's mock our device since we don't need it in our test
    device = MiniTest::Mock.new

    # create mock adapter, that says 'ohai!' on notify
    mock_adapter = MiniTest::Mock.new
    mock_adapter.expect :notify, 'ohai!'

    # connect our mock, so next NoficationAdapter.new call will return our mock
    # and not usual implementation
    NotificationAdapter.stub :new, mock_adapter do
      NotifiesUser.notify(device).must_equal 'ohai!'
    end
  end
end

祝你有愉快的一天!

P.S。如果您想了解更多关于隔离测试、模拟技术和一般 "fast rails tests" 运动的信息,我强烈推荐您观看 Gary Bernhardt 的 destroyallsoftware.com 截屏视频。这是付费的东西,但是非常值得。