-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Implement send_email matcher #2670
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
Feature: `send_email` matcher | ||
|
||
The `send_email` matcher is used to check if an email with the given parameters has been sent inside the expectation block. | ||
|
||
NOTE: It implies that the spec example actually sends the email using the test adapter and does not schedule it for background. | ||
|
||
To have an email sent in tests make sure: | ||
- `ActionMailer` performs deliveries - `Rails.application.config.action_mailer.perform_deliveries = true` | ||
- If the email is sent asynchronously (with `.deliver_later` call), ActiveJob uses the inline adapter - `Rails.application.config.active_job.queue_adapter = :inline` | ||
- ActionMailer uses the test adapter - `Rails.application.config.action_mailer.delivery_method = :test` | ||
|
||
If you want to check an email has been scheduled for background, use the `have_enqueued_email` matcher. | ||
JonRowe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Scenario: Checking email sent with the given multiple parameters | ||
Given a file named "spec/mailers/notifications_mailer_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
|
||
RSpec.describe NotificationsMailer do | ||
it "checks email sending by multiple params" do | ||
expect { | ||
NotificationsMailer.signup.deliver_now | ||
}.to send_email( | ||
from: '[email protected]', | ||
to: '[email protected]', | ||
subject: 'Signup' | ||
) | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/mailers/notifications_mailer_spec.rb` | ||
Then the examples should all pass | ||
|
||
Scenario: Checking email sent with matching parameters | ||
Given a file named "spec/mailers/notifications_mailer_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
|
||
RSpec.describe NotificationsMailer do | ||
it "checks email sending by one param only" do | ||
expect { | ||
NotificationsMailer.signup.deliver_now | ||
}.to send_email( | ||
to: '[email protected]' | ||
) | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/mailers/notifications_mailer_spec.rb` | ||
Then the examples should all pass | ||
|
||
Scenario: Checking email not sent with the given parameters | ||
Given a file named "spec/mailers/notifications_mailer_spec.rb" with: | ||
"""ruby | ||
require "rails_helper" | ||
|
||
RSpec.describe NotificationsMailer do | ||
it "checks email not sent" do | ||
expect { | ||
NotificationsMailer.signup.deliver_now | ||
}.to_not send_email( | ||
to: '[email protected]' | ||
) | ||
end | ||
end | ||
""" | ||
When I run `rspec spec/mailers/notifications_mailer_spec.rb` | ||
Then the examples should all pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# frozen_string_literal: true | ||
|
||
module RSpec | ||
module Rails | ||
module Matchers | ||
# @api private | ||
# | ||
# Matcher class for `send_email`. Should not be instantiated directly. | ||
# | ||
# @see RSpec::Rails::Matchers#send_email | ||
class SendEmail < RSpec::Rails::Matchers::BaseMatcher | ||
# @api private | ||
# Define the email attributes that should be included in the inspection output. | ||
INSPECT_EMAIL_ATTRIBUTES = %i[subject from to cc bcc].freeze | ||
|
||
def initialize(criteria) | ||
@criteria = criteria | ||
end | ||
|
||
# @api private | ||
def supports_value_expectations? | ||
false | ||
end | ||
|
||
# @api private | ||
def supports_block_expectations? | ||
ka8725 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
true | ||
end | ||
|
||
def matches?(block) | ||
define_matched_emails(block) | ||
|
||
@matched_emails.one? | ||
end | ||
|
||
# @api private | ||
# @return [String] | ||
def failure_message | ||
result = | ||
if multiple_match? | ||
"More than 1 matching emails were sent." | ||
else | ||
"No matching emails were sent." | ||
end | ||
"#{result}#{sent_emails_message}" | ||
end | ||
|
||
# @api private | ||
# @return [String] | ||
def failure_message_when_negated | ||
"Expected not to send an email but it was sent." | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
private | ||
|
||
def diffable? | ||
true | ||
end | ||
|
||
def deliveries | ||
ActionMailer::Base.deliveries | ||
end | ||
|
||
def define_matched_emails(block) | ||
before = deliveries.dup | ||
|
||
block.call | ||
|
||
after = deliveries | ||
|
||
@diff = after - before | ||
@matched_emails = @diff.select(&method(:matched_email?)) | ||
end | ||
|
||
def matched_email?(email) | ||
@criteria.all? do |attr, value| | ||
expected = | ||
case attr | ||
when :to, :from, :cc, :bcc then Array(value) | ||
else | ||
value | ||
end | ||
|
||
values_match?(expected, email.public_send(attr)) | ||
end | ||
end | ||
|
||
def multiple_match? | ||
@matched_emails.many? | ||
end | ||
|
||
def sent_emails_message | ||
if @diff.empty? | ||
"\n\nThere were no any emails sent inside the expectation block." | ||
else | ||
sent_emails = | ||
@diff.map do |email| | ||
inspected = INSPECT_EMAIL_ATTRIBUTES.map { |attr| "#{attr}: #{email.public_send(attr)}" }.join(", ") | ||
"- #{inspected}" | ||
end.join("\n") | ||
"\n\nThe following emails were sent:\n#{sent_emails}" | ||
end | ||
end | ||
end | ||
|
||
# @api public | ||
# Check email sending with specific parameters. | ||
# | ||
# @example Positive expectation | ||
# expect { action }.to send_email | ||
# | ||
# @example Negative expectations | ||
# expect { action }.not_to send_email | ||
# | ||
# @example More precise expectation with attributes to match | ||
# expect { action }.to send_email(to: '[email protected]', subject: 'Confirm email') | ||
def send_email(criteria = {}) | ||
SendEmail.new(criteria) | ||
end | ||
end | ||
end | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
RSpec.describe "send_email" do | ||
let(:mailer) do | ||
Class.new(ActionMailer::Base) do | ||
self.delivery_method = :test | ||
|
||
def test_email | ||
mail( | ||
from: "[email protected]", | ||
cc: "[email protected]", | ||
bcc: "[email protected]", | ||
to: "[email protected]", | ||
subject: "Test email", | ||
body: "Test email body" | ||
) | ||
end | ||
end | ||
end | ||
|
||
it "checks email sending by all params together" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email( | ||
from: "[email protected]", | ||
to: "[email protected]", | ||
cc: "[email protected]", | ||
bcc: "[email protected]", | ||
subject: "Test email", | ||
body: a_string_including("Test email body") | ||
) | ||
end | ||
|
||
it "checks email sending by no params" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email | ||
end | ||
|
||
it "with to_not" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to_not send_email( | ||
from: "[email protected]" | ||
) | ||
end | ||
|
||
it "fails with a clear message" do | ||
expect { | ||
expect { mailer.test_email.deliver_now }.to send_email(from: '[email protected]') | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
|
||
The following emails were sent: | ||
- subject: Test email, from: ["[email protected]"], to: ["[email protected]"], cc: ["[email protected]"], bcc: ["[email protected]"] | ||
MSG | ||
end | ||
|
||
it "fails with a clear message when no emails were sent" do | ||
expect { | ||
expect { }.to send_email | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
|
||
There were no any emails sent inside the expectation block. | ||
MSG | ||
end | ||
|
||
it "fails with a clear message for negated version" do | ||
expect { | ||
expect { mailer.test_email.deliver_now }.to_not send_email(from: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, "Expected not to send an email but it was sent.") | ||
end | ||
pirj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
it "fails for multiple matches" do | ||
expect { | ||
expect { 2.times { mailer.test_email.deliver_now } }.to send_email(from: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
More than 1 matching emails were sent. | ||
|
||
The following emails were sent: | ||
- subject: Test email, from: ["[email protected]"], to: ["[email protected]"], cc: ["[email protected]"], bcc: ["[email protected]"] | ||
- subject: Test email, from: ["[email protected]"], to: ["[email protected]"], cc: ["[email protected]"], bcc: ["[email protected]"] | ||
MSG | ||
end | ||
|
||
context "with compound matching" do | ||
it "works when both matchings pass" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "[email protected]").and send_email(from: "[email protected]") | ||
}.to_not raise_error | ||
end | ||
|
||
it "works when first matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "[email protected]").and send_email(to: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
|
||
The following emails were sent: | ||
- subject: Test email, from: ["[email protected]"], to: ["[email protected]"], cc: ["[email protected]"], bcc: ["[email protected]"] | ||
MSG | ||
end | ||
|
||
it "works when second matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "[email protected]").and send_email(to: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip) | ||
No matching emails were sent. | ||
|
||
The following emails were sent: | ||
- subject: Test email, from: ["[email protected]"], to: ["[email protected]"], cc: ["[email protected]"], bcc: ["[email protected]"] | ||
MSG | ||
end | ||
end | ||
|
||
context "with a custom negated version defined" do | ||
define_negated_matcher :not_send_email, :send_email | ||
|
||
it "works with a negated version" do | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to not_send_email( | ||
from: "[email protected]" | ||
) | ||
end | ||
|
||
it "fails with a clear message" do | ||
expect { | ||
expect { mailer.test_email.deliver_now }.to not_send_email(from: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, "Expected not to send an email but it was sent.") | ||
end | ||
|
||
context "with a compound negated version" do | ||
it "works when both matchings pass" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to not_send_email(to: "[email protected]").and not_send_email(from: "[email protected]") | ||
}.to_not raise_error | ||
end | ||
|
||
it "works when first matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to not_send_email(to: "[email protected]").and send_email(to: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, a_string_including(<<~MSG.strip)) | ||
Expected not to send an email but it was sent. | ||
MSG | ||
end | ||
|
||
it "works when second matching fails" do | ||
expect { | ||
expect { | ||
mailer.test_email.deliver_now | ||
}.to send_email(to: "[email protected]").and not_send_email(to: "[email protected]") | ||
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, a_string_including(<<~MSG.strip)) | ||
Expected not to send an email but it was sent. | ||
MSG | ||
end | ||
end | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.