rspec 测试时,ActionMailer 方法调用在模块中返回 nil

Posted

技术标签:

【中文标题】rspec 测试时,ActionMailer 方法调用在模块中返回 nil【英文标题】:ActionMailer method call returning nil in module while rspec testing 【发布时间】:2012-04-10 15:37:47 【问题描述】:

我有一个ActionMailer 课程

class UserMailer < ActionMailer::Base 
  default from: "no-reply@spicy-millennium.com" 

  def submission_reminder user 
    @user = user           
    mail :to => user.email, :subject => "Your timesheet needs to be submitted!" 
  end    
end

如果我在开发中调用UserMailer.submission_reminder(current_user),它会返回一个Mail::Message 对象,就像预期的那样。

我的应用程序中调用此方法的位置在我的 lib 文件夹中的一个模块中:

module TimesheetSubmissionNotifier                            
  def self.send_submission_reminders
    User.all.each  |user| UserMailer.submission_reminder(user).deliver 
  end
end

当我在开发中调用TimesheetSubmissionNotifier.send_submission_reminders 时,UserMailer.submission_remind(user) 返回邮件消息并调用传递,一切正常。

问题是当我通过 rspec 测试调用 TimesheetSubmissionNotifier.send_submission_reminders 时,UserMailer.submission_reminder(user) 返回 nil。

如果我直接从 rspec 测试中调用 UserMailer.submission_reminder(user),它会按预期返回邮件消息。

这是我的 config/environment/test.rb 中唯一与 ActionMailer 相关的行:

config.action_mailer.delivery_method = :test 
config.action_mailer.default_url_options =  :host => 'localhost:3000' 

任何想法为什么该方法返回 nil?

【问题讨论】:

当你说直接从 rspec 测试调用 UserMailer.submission_reminder(user) 时,你是如何初始化你传递给方法的 user 对象的?您是从用户表中获取它还是使用工厂等构建它? 【参考方案1】:

对于那些有类似问题的人,我找到了问题。

我使用的是should_receive RSpec 期望,我没有意识到它实际上创建了它所在类的模拟。所以我完全模拟了 UserMailer 类,这意味着它永远不会到达实际的 UserMailer 类。

我无法通过使用模拟函数让它工作,所以我改变了我的测试来查看 UserMailer.deliveries 存储并检查是否有正确数量的消息被放入其中并且它们被发送到正确的电子邮件地址。

【讨论】:

是的,我也这样做了,有一个简单的谷歌示例可以做到这一点。值得警告人们这是一种非常错误的测试方法。 should_receive 正在模拟一个方法端点,但没有定义任何类型的返回值,除非你明确地这样做(接收与接收电子邮件无关;-))。 从技术上讲, should_receive 不会创建模拟对象,它会存根方法并返回 nil(rspec 文档将此称为部分模拟)。您可以使用 and_call_original 调用原始方法。例如。 should_receive(:submission_reminder).and_call_original 我们在升级到 rails 4 时遇到了问题。如果邮件程序返回 nil,我们使用了在升级期间更改的 sidekiq 延迟以引发 RuntimeError 'returned an undeliverable mail object'。 Mailer.should_receive(:submission_reminder).and_call_original 修复了测试。 Mailer.should_receive(:submission_reminder).and_call_original 是正式的旧语法。如果可以避免的话,尽量不要and_call_original。最好使用空的 Openstruct 来加快您的规范,在单元测试中的想法是模拟所有不接触您正在测试的对象的东西

以上是关于rspec 测试时,ActionMailer 方法调用在模块中返回 nil的主要内容,如果未能解决你的问题,请参考以下文章

在 RSpec 测试后清除 ActionMailer::Base.deliveries

Rails 4, RSpec 3.2 - 如何模拟 ActionMailer 的 Deliver_now 方法来引发异常

如何使用 RSpec 测试电子邮件标头

集成测试ActionMailer和ActiveJob

Rails/Rspec:测试延迟作业邮件

Rspec - 如何测试邮件是不是使用正确的模板