Home > Mobile >  How to best use rspec mocks when using rails mailer parameterization
How to best use rspec mocks when using rails mailer parameterization

Time:06-23

I'm currently changing our rails mailers to use the newer way of using the mailer that uses parameterization, which brings our code base inline with the rails guide, but more importantly it also allows the parameters to be filtered appropriately in the logs and 3rd party apps like AppSignal.

ie. I'm changing this

UserMailer.new_user_email(user).deliver_later

to

UserMailer.with(user: user).new_user_email.deliver_later

But we have a quite a few specs that use Rspec Mocks to confirm that a mailer was called with the appropriate params. Generally these test that a controller actually asked the mailer to spend the email correctly.

We generally have something like:

        expect(UserMailer).to receive(:new_user_email)
                                .with(user)
                                .and_return(OpenStruct.new(deliver_later: true))
                                .once

But now with the parameterization of the mailer, I don't see any easy way to use rspec mocks to verify that the correct mailer method was called with the correct params. Does anyone have any ideas on how best to test this now? Readability of the expectation is probably the biggest factor here, ideally it is one line without multiple lines of mocking setup.

Note: that I don't really want to actually run the mailer, we have mailer unit specs that test the actual mailer is working.

CodePudding user response:

When you have couple of methods which are chained you can use receive_message_chain But there is one backdraw - it doesn't support the whole fluent interface of counters like once twice

So you have to do one trick here:

# set counter manually
counter = 0

expect(UserMailer).to receive_message_chain(
  :with, :new_user_email
).with(user: user).with(no_args).and_return(OpenStruct.new(deliver_later: true)) do
  counter  = 1
end

# Very important: Here must be call of your method which triggers `UserMailer` mailer. For example
UserNotifier.notify_user(user)

expect(counter).to eq(1)
# class for example
class UserNotifier
  def self.notify_user(user)
    UserMailer.with(user: user).new_user_email.deliver_later
  end
end

CodePudding user response:

So for anyone else that hits this problem in the future. I ended up adding a helper method in the specs/support directory with something like this

def expect_mailer_call(mailer, action, params, delivery_method = :deliver_later)
  mailer_double = instance_double(mailer)
  message_delivery_double = instance_double(ActionMailer::MessageDelivery)
  expect(mailer).to receive(:with).with(params).and_return(mailer_double)
  expect(mailer_double).to receive(action).with(no_args).and_return(message_delivery_double)
  expect(message_delivery_double).to receive(delivery_method).once
end

Which can then be called in a spec like this

expect_mailer_call(UserMailer, :new_user_email, { to:'[email protected]', name: kind_of(String) })

or for deliver_now

expect_mailer_call(UserMailer, :new_user_email, { to:'[email protected]', name: kind_of(String) }, :deliver_now)

It works good enough for our situation, but you might need to adapt it and add a part for the amount of emails or something if you need to configure the once restriction.

  • Related