【问题标题】:Rspec: Testing to make sure Notification Email is *not* sent after invalid record (ActiveRecord::RecordInvalid:)Rspec:测试以确保在无效记录后*不*发送通知电子邮件 (ActiveRecord::RecordInvalid:)
【发布时间】:2015-09-29 04:23:37
【问题描述】:

我正在尝试测试以确保通知邮件在无效记录后没有发送,但在测试完成之前我一直收到以下错误

"ActiveRecord::RecordInvalid:

      it 'does not call send_email_notification' do
        expect(NotificationMailer).not_to receive(:user_notification)
        FactoryGirl.create(:invalid_user, shop: shop)
      end

如何正确测试?

编辑:这是发送邮件的代码:

after_create :send_email_notification


  private


  def send_email_notification
    if self.shop.email_notifications
        NotificationMailer.user_notification(self).deliver_now
    end
  end
end

【问题讨论】:

  • 由于触发了异常,这意味着存在验证问题,这是您尝试保存无效用户后的预期行为。这本身就确保没有邮件被发送。问题是您在代码中的哪个位置声明了发送电子邮件?是after_save,还是create?
  • 创建后。那么我将如何为此编写规范?期望无效用户无效?
  • 你能分享你实际发送邮件的代码部分吗?

标签: ruby-on-rails rspec


【解决方案1】:
it 'does not send notification email when user is invalid' do
  expect(NotificationMailer).not_to receive(:user_notification)
  post :create, user: attributes_for(:invalid_user)
end

所以,这样做的目的是按照您的方式设置您的期望,然后 postuser_controller create 方法invalid_user 属性。

当然,如果您在用户模型中正确设置了验证,并且随后不调用 NotificationMailer.user_notification,则不应允许帖子创建记录。

请注意,attributes_for 是另一种 FactoryGirl 方法,您可以使用它来安排工厂属性并将其作为控制器参数传递。

现在!为什么它不适用于您原来的方法? 这是因为 FactoryGirl 抱怨它无法创建记录,这是绝对合乎逻辑的,因为您试图创建一个无效用户。失败的错误与测试您的电子邮件通知无关,而是与您设置工厂的方式有关。

最后一点!如果您运行测试并且它抱怨:

"NoMethodError: undefined method `post' for #<RSpec::ExampleGroups"

这可能意味着您的规范文件不在规范/控制器下。

post、create、patch、delete 方法是 RSpec::Rails::ControllerExampleGroup 的一部分

要解决这个问题,请参考下面的 Stackoverflow answer

希望这会有所帮助。

【讨论】:

  • 等等 - 现在我收到“NoMethodError: undefined method `post' for #<:examplegroups>
  • 这可能是因为您的规范不在规范/控制器之下。
  • 我已编辑答案以包含指向 stackoverflow 答案的链接,该答案演示了如何解决此问题的说明。
【解决方案2】:

以下是我用来测试您的用例的一些代码:您可以将其复制并粘贴到一个文件中,然后在其上运行rspec。我希望我对您未披露的 Rails 应用程序部分所做的假设不会离题太远。

require 'active_record'
require 'factory_girl'
require 'rspec'

ActiveRecord::Base.establish_connection(
  adapter: 'sqlite3', database: ':memory:'
)

class User < ActiveRecord::Base
  has_one :shop
  validates :email, presence: true
  after_create :send_notification

  private

  def send_notification
    if shop.email_notifications
      NotificationMailer.user_notification(self).deliver_now
    end
  end
end

class Shop < ActiveRecord::Base
  belongs_to :user
end

ActiveRecord::Schema.define do
  create_table :users do |t|
    t.string :email
  end

  create_table :shops do |t|
    t.boolean :email_notifications
    t.belongs_to :user
  end
end

FactoryGirl.define do
  factory :user do
    email "test@example.com"
    shop

    factory :invalid_user do
      email nil
    end
  end

  factory :shop do
    email_notifications true
  end
end

RSpec.describe User do
  context 'on save' do
    let(:mailer) { double('mailer') }

    before do
      # You probably won't need this stub_const since the class will exist
      # in your app
      stub_const('NotificationMailer', Class.new)
      allow(NotificationMailer).to \
        receive(:user_notification).with(user).and_return(mailer)
    end

    context 'when user is valid' do
      let(:user) { FactoryGirl.build(:user) }

      it 'calls to send email notifications' do
        expect(mailer).to receive(:deliver_now)
        user.save
      end
    end

    context 'when user is invalid' do
      let(:user) { FactoryGirl.build(:invalid_user) }

      it 'does not call to send email notifications' do
        expect(mailer).to_not receive(:deliver_now)
        user.save
      end
    end
  end
end

由于您的回调中有一个外部依赖项(对单独类 NotificationMailer 的调用),您可能需要将消息存根到该依赖项以使测试通过,否则您可能会得到 @987654326 @ 值在您可能没想到时返回(有关详细信息,请参阅 this blog post)。

只是一个意见,但如果你只在there are no external dependencies in them and the logic only refers to state internal to the object(在这种情况下为User)时使用回调,你甚至可能会帮到你一个忙。您所做的更改类似于将NotificationMailer.user_notification(self).deliver_now 调用从User 模型回调中移到控制器中(我假设)您正在调用以保存用户。提取可能类似于:

def create
  @user = User.new(user_params)
  if @user.save
    NotificationMailer.user_notification(@user).deliver_now
    # do other stuff, render, redirect etc
  else
    # do something else
  end
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多