-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add matcher support to assert_enqueued_email_with
#46626
Add matcher support to assert_enqueued_email_with
#46626
Conversation
7ddc566
to
5a9d6b9
Compare
assert_enqueued_email_with
It would be nice if this could support skipping the arg/param checking if nothing is provided. I have often wanted to test that the expected Mailer method was enqueued, but I don't particularly care to check the args/params (this is especially annoying if you use named parameters for mailer methods as you have to match the string-keyed hash). assert_enqueued_email_with ManagerMailer, :bounced_email do
ManagerMailer.bounced_email(
plan: plan,
to_user: user,
subject: message[:subject],
error: description
).deliver_later
end
`` |
@swanson Wouldn't I do like the idea regardless, and wouldn't mind adding support for this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a helpful addition. 👍
expected_args = if args.is_a?(Proc) || params.is_a?(Proc) | ||
-> (job_args) { | ||
# Ordered from the least to the most heavy checks. | ||
return false if [mailer.to_s, method.to_s, "deliver_now"] != job_args[0..2] | ||
return false if !params.is_a?(Proc) && params != job_args[3][:params] | ||
return false if !args.is_a?(Proc) && Array(args) != job_args[3][:args] | ||
return false if args.is_a?(Proc) && !args.(job_args[3][:args]) | ||
return false if params.is_a?(Proc) && !params.(job_args[3][:params]) | ||
|
||
true | ||
} | ||
else | ||
[mailer.to_s, method.to_s, "deliver_now", args: Array(args)] | ||
if args.is_a?(Hash) | ||
[mailer.to_s, method.to_s, "deliver_now", params: args, args: []] | ||
elsif params.present? | ||
[mailer.to_s, method.to_s, "deliver_now", params: params, args: Array(args)] | ||
else | ||
[mailer.to_s, method.to_s, "deliver_now", args: Array(args)] | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was a bit hard for me to follow. I've pushed a commit that unifies the logic. Let me know what you think. If you like it, you or I can rebase and squash the commits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonathanhefner Thanks for the review. Your version is much smaller, which is always a win. 👍🏻 However, it raised a couple of questions.
expected_args = if args.is_a?(Proc) || params.is_a?(Proc) | ||
-> (job_args) { | ||
# Ordered from the least to the most heavy checks. | ||
return false if [mailer.to_s, method.to_s, "deliver_now"] != job_args[0..2] | ||
return false if !params.is_a?(Proc) && params != job_args[3][:params] | ||
return false if !args.is_a?(Proc) && Array(args) != job_args[3][:args] | ||
return false if args.is_a?(Proc) && !args.(job_args[3][:args]) | ||
return false if params.is_a?(Proc) && !params.(job_args[3][:params]) | ||
params, args = args, nil if args.is_a?(Hash) | ||
args = Array(args) unless args.is_a?(Proc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't we want to support a situation when both args and params are passed in, even if args is a Hash?
The original implementation didn't seem to support it, so my PR was proposing to carefully fix it (I thought may've been a bug) alongside adding support for procs, but without breaking existing tests. The bug fix would only kick in if either args or params is a proc (something that wasn't supported before). It would switch to the "new mode" of testing. In the "new mode" if you put hash in either of them, it gets tested.
Your simplification makes it such that if you try passing args: { foo: 'bar' }, params: ->(_){ … }
, then expected params are overwritten with expected args and get compared against actual params, so the original expected params (the proc) is dropped. Additionally, expected args become []
, so actual args are still being compared, but against an empty array. Is that desirable? Or am I misreading this? 🤔
P.S. I think this is why I ended up writing it that way. 😬 I seem to remember my original attempt was much shorter, but didn't handle this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't we want to support a situation when both expected args and params are passed in, even if args is a Hash?
The original implementation didn't seem to support it, so my PR was proposing to carefully fix it (I thought may've been a bug) alongside adding support for procs
I have not dug into why the original code was written that way, but it feels like a separate concern, so I think it would be better to address it in another PR.
The bug fix would only kick in if either args or params is a proc (something that wasn't supported before). It would switch to the "new mode" of testing.
That seems unintuitive to me. I think if we change the behavior, we should change it for all types of arguments.
Part of the reason I factored params = ...
and args = ...
out from the comparison logic is to make that kind of change easier, as well as implementing the suggestion from #46626 (comment). (But, again, as a separate PR with a dedicated discussion.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not dug into why the original code was written that way, but it feels like a separate concern, so I think it would be better to address it in another PR. […] I think if we change the behavior, we should change it for all types of arguments.
I guess depends how you think about it.
Option 1: Add new feature "correctly", then fix old feature as a breaking change.
Option 2: Add new feature "brokenly", but consistently with old feature, then fix both of them with a breaking change for both.
I honestly prefer option 1, even though it feels a bit unintuitive. It minimizes unintended behavior.
And since this doesn't seem urgent, we also have these:
Option 3: dig into old feature, fix it, then add this new feature
Option 4: fix them all at once
Since I'm not part of the Rails team, I can only provide reasoning/feedback, but ultimately respect whatever decision you make. Let me know if you want to go option 2, and I can help with the rebase/squash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The :params
kwarg was introduced in #45752. The lesser precedence of :params
vs :args
seems to be intentional. Code that specifies :params
is expected to specify :args
as an array (if at all).
I think that is a reasonable expectation, so I would not call it "broken". Though I agree it also isn't ideal. #45752 mentions possibly deprecating the old behavior. I have submitted #48194 to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, yeah I was calling it "broken" for brevity, definitely a nuanced case. Nice, very efficient work. 🎉👍🏻
Yes, I usually just end up with |
Support args/params proc matchers in `assert_enqueued_email_with` to make it behave more like `assert_enqueued_with`. Fix rails#46621. Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
a6e7157
to
96f19e9
Compare
I rebased and squashed, and submitted #48194 as a follow-up (see #46626 (comment)). Thank you, @maxim! 📬 |
Motivation / Background
Currently
assert_enqueued_with
allows you to pass procs for matchingargs
.However,
assert_enqueued_email_with
doesn't let you do that forargs
andparams
.I propose that we should.
For example, if you are passing a generated token to an email, you would have to mock/stub your way to verify that it looks right in job args. Or you have to reach for the lower level
assert_enqueued_with
helper. With this change you could simply do something like this:Fixes #46621.
Detail
Since
assert_enqueued_email_with
tries to be clever about figuring out whetherargs
areparams
, I decided to leave that logic completely intact (to preserve current behavior), and add another conditional branch which only kicks in if either args or params are a proc. In such a case, it never acts clever about args/params, and simply passes down a lambda that fails if any job args don't match, and succeeds if they do.One unrelated question: why are we currently overwriting the passed-in params? Is that a bug?
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]