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.