Rspec:测试以确保在无效记录后发送通知电子邮件(ActiveRecord :: RecordInvalid :)

时间:2015-09-25 23:37:23

标签: ruby-on-rails rspec

我正在尝试测试以确保通知邮件程序在无效记录之后没有发送但我在测试完成之前一直低于错误

  

“的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

2 个答案:

答案 0 :(得分:2)

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

所以,这样做是为了让您按照自己的方式做出预期,然后发布 user_controller 创建方法 invalid_user 属性。

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

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

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

最后的说明!如果您运行测试并抱怨:

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

这可能意味着您的spec文件不在spec / controllers下。

发布,创建,修补,删除方法是RSpec::Rails::ControllerExampleGroup

的一部分

要解决此问题,请参阅以下Stackoverflow answer

希望这有帮助。

答案 1 :(得分:0)

下面是我用来测试您的用例的一些代码:您可以将其复制并粘贴到文件中并在其上运行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的标注),你可能需要将消息存根到该依赖项以使测试通过,否则你可能会得到当您可能不期望它们时会返回nil个值(有关详细信息,请参阅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