Skip to content

Commit 8840963

Browse files
jamesdabbs-procoreSam Phippen
authored andcommitted
Add ActionMailbox spec helpers and test type (#2119)
* Add ActionMailbox spec helpers and test type Adds the following helpers to example groups with `:type => :mailbox` * process(mail_or_attributes) - send mail directly to the mailbox under test for `process`ing. * receive_inbound_email(mail_or_attributes) - matcher for asserting whether incoming email would route to the mailbox under test. * have_been_delivered - matcher for asserting whether an incoming email object was delivered. * have_bounced - matcher for asserting whether an incoming email object has bounced. * have_failed - matcher for asserting whether an incoming email object has failed. Also adds an ActionMailbox test generator * Add style changes from code review
1 parent b4a2682 commit 8840963

File tree

11 files changed

+328
-1
lines changed

11 files changed

+328
-1
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require 'generators/rspec'
2+
3+
module Rspec
4+
module Generators
5+
# @private
6+
class MailboxGenerator < Base
7+
def create_mailbox_spec
8+
template('mailbox_spec.rb.erb',
9+
File.join('spec/mailboxes', class_path, "#{file_name}_mailbox_spec.rb")
10+
)
11+
end
12+
end
13+
end
14+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'rails_helper'
2+
3+
<% module_namespacing do -%>
4+
RSpec.describe <%= class_name %>Mailbox, <%= type_metatag(:mailbox) %> do
5+
pending "add some examples to (or delete) #{__FILE__}"
6+
end
7+
<% end -%>

lib/rspec/rails/configuration.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class Configuration
3434
:routing => %w[spec routing],
3535
:view => %w[spec views],
3636
:feature => %w[spec features],
37-
:system => %w[spec system]
37+
:system => %w[spec system],
38+
:mailbox => %w[spec mailboxes]
3839
}
3940

4041
# Sets up the different example group modules for the different spec types
@@ -140,6 +141,10 @@ def filter_rails_from_backtrace!
140141
if defined?(ActiveJob)
141142
config.include RSpec::Rails::JobExampleGroup, :type => :job
142143
end
144+
145+
if defined?(ActionMailbox)
146+
config.include RSpec::Rails::MailboxExampleGroup, :type => :mailbox
147+
end
143148
end
144149
# rubocop:enable Style/MethodLength
145150

lib/rspec/rails/example.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
require 'rspec/rails/example/job_example_group'
1010
require 'rspec/rails/example/feature_example_group'
1111
require 'rspec/rails/example/system_example_group'
12+
require 'rspec/rails/example/mailbox_example_group'
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
module RSpec
2+
module Rails
3+
# @api public
4+
# Container module for mailbox spec functionality.
5+
module MailboxExampleGroup
6+
extend ActiveSupport::Concern
7+
8+
if RSpec::Rails::FeatureCheck.has_action_mailbox?
9+
require 'action_mailbox/test_helper'
10+
extend ::ActionMailbox::TestHelper
11+
12+
def self.create_inbound_email(arg)
13+
case arg
14+
when Hash
15+
create_inbound_email_from_mail(arg)
16+
else
17+
create_inbound_email_from_source(arg.to_s)
18+
end
19+
end
20+
else
21+
def self.create_inbound_email(_arg)
22+
raise "Could not load ActionMailer::TestHelper"
23+
end
24+
end
25+
26+
class_methods do
27+
# @private
28+
def mailbox_class
29+
described_class
30+
end
31+
end
32+
33+
included do
34+
subject { described_class }
35+
end
36+
37+
# Verify the status of any inbound email
38+
#
39+
# @example
40+
# describe ForwardsMailbox do
41+
# it "can describe what happened to the inbound email" do
42+
# mail = process(args)
43+
#
44+
# # can use any of:
45+
# expect(mail).to have_been_delivered
46+
# expect(mail).to have_bounced
47+
# expect(mail).to have_failed
48+
# end
49+
# end
50+
def have_been_delivered
51+
satisfy('have been delivered', &:delivered?)
52+
end
53+
54+
def have_bounced
55+
satisfy('have bounced', &:bounced?)
56+
end
57+
58+
def have_failed
59+
satisfy('have failed', &:failed?)
60+
end
61+
62+
# Process an inbound email message directly, bypassing routing.
63+
#
64+
# @param message [Hash, Mail::Message] a mail message or hash of
65+
# attributes used to build one
66+
# @return [ActionMaibox::InboundMessage]
67+
def process(message)
68+
MailboxExampleGroup.create_inbound_email(message).tap do |mail|
69+
self.class.mailbox_class.receive(mail)
70+
end
71+
end
72+
end
73+
end
74+
end

lib/rspec/rails/feature_check.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def has_action_mailer_show_preview?
3838
::ActionMailer::Base.respond_to?(:show_previews=)
3939
end
4040

41+
def has_action_mailbox?
42+
defined?(::ActionMailbox)
43+
end
44+
4145
def has_1_9_hash_syntax?
4246
::Rails::VERSION::STRING > '4.0'
4347
end

lib/rspec/rails/matchers.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ module Matchers
2020
require 'rspec/rails/matchers/relation_match_array'
2121
require 'rspec/rails/matchers/be_valid'
2222
require 'rspec/rails/matchers/have_http_status'
23+
2324
if RSpec::Rails::FeatureCheck.has_active_job?
2425
require 'rspec/rails/matchers/active_job'
2526
end
27+
28+
if RSpec::Rails::FeatureCheck.has_action_mailbox?
29+
require 'rspec/rails/matchers/action_mailbox'
30+
end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module RSpec
2+
module Rails
3+
module Matchers
4+
# Namespace for various implementations of ActionMailbox features
5+
#
6+
# @api private
7+
module ActionMailbox
8+
# @private
9+
class Base < RSpec::Rails::Matchers::BaseMatcher
10+
private
11+
12+
def create_inbound_email(message)
13+
RSpec::Rails::MailboxExampleGroup.create_inbound_email(message)
14+
end
15+
end
16+
17+
# @private
18+
class ReceiveInboundEmail < Base
19+
def initialize(message)
20+
super()
21+
22+
@inbound_email = create_inbound_email(message)
23+
end
24+
25+
def matches?(mailbox)
26+
@mailbox = mailbox
27+
@receiver = ApplicationMailbox.router.send(:match_to_mailbox, inbound_email)
28+
29+
@receiver == @mailbox
30+
end
31+
32+
def failure_message
33+
"expected #{describe_inbound_email} to route to #{mailbox}".tap do |msg|
34+
if receiver
35+
msg << ", but routed to #{receiver} instead"
36+
end
37+
end
38+
end
39+
40+
def failure_message_when_negated
41+
"expected #{describe_inbound_email} not to route to #{mailbox}"
42+
end
43+
44+
private
45+
46+
attr_reader :inbound_email, :mailbox, :receiver
47+
48+
def describe_inbound_email
49+
"mail to #{inbound_email.mail.to.to_sentence}"
50+
end
51+
end
52+
end
53+
54+
# @api public
55+
# Passes if the given inbound email would be routed to the subject inbox.
56+
#
57+
# @param message [Hash, Mail::Message] a mail message or hash of
58+
# attributes used to build one
59+
def receive_inbound_email(message)
60+
ActionMailbox::ReceiveInboundEmail.new(message)
61+
end
62+
end
63+
end
64+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generators are not automatically loaded by Rails
2+
require 'generators/rspec/mailbox/mailbox_generator'
3+
require 'support/generators'
4+
5+
RSpec.describe Rspec::Generators::MailboxGenerator, :type => :generator, :skip => !RSpec::Rails::FeatureCheck.has_action_mailbox? do
6+
setup_default_destination
7+
8+
describe 'the generated files' do
9+
before { run_generator %w[forwards] }
10+
11+
subject { file('spec/mailboxes/forwards_mailbox_spec.rb') }
12+
13+
it { is_expected.to exist }
14+
it { is_expected.to contain(/require 'rails_helper'/) }
15+
it { is_expected.to contain(/describe ForwardsMailbox, #{type_metatag(:mailbox)}/) }
16+
17+
end
18+
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require "spec_helper"
2+
require "rspec/rails/feature_check"
3+
4+
class ApplicationMailbox
5+
class << self
6+
attr_accessor :received
7+
8+
def receive(*)
9+
self.received += 1
10+
end
11+
end
12+
13+
self.received = 0
14+
end
15+
16+
module RSpec
17+
module Rails
18+
describe MailboxExampleGroup, :skip => !RSpec::Rails::FeatureCheck.has_active_job? do
19+
it_behaves_like "an rspec-rails example group mixin", :mailbox,
20+
'./spec/mailboxes/', '.\\spec\\mailboxes\\'
21+
22+
def group_for(klass)
23+
RSpec::Core::ExampleGroup.describe klass do
24+
include MailboxExampleGroup
25+
end
26+
end
27+
28+
let(:group) { group_for(::ApplicationMailbox) }
29+
let(:example) { group.new }
30+
31+
describe '#have_been_delivered' do
32+
it 'raises on undelivered mail' do
33+
expect {
34+
expect(double('IncomingEmail', :delivered? => false)).to example.have_been_delivered
35+
}.to raise_error(/have been delivered/)
36+
end
37+
38+
it 'does not raise otherwise' do
39+
expect(double('IncomingEmail', :delivered? => true)).to example.have_been_delivered
40+
end
41+
end
42+
43+
describe '#have_bounced' do
44+
it 'raises on unbounced mail' do
45+
expect {
46+
expect(double('IncomingEmail', :bounced? => false)).to example.have_bounced
47+
}.to raise_error(/have bounced/)
48+
end
49+
50+
it 'does not raise otherwise' do
51+
expect(double('IncomingEmail', :bounced? => true)).to example.have_bounced
52+
end
53+
end
54+
55+
describe '#have_failed' do
56+
it 'raises on unfailed mail' do
57+
expect {
58+
expect(double('IncomingEmail', :failed? => false)).to example.have_failed
59+
}.to raise_error(/have failed/)
60+
end
61+
62+
it 'does not raise otherwise' do
63+
expect(double('IncomingEmail', :failed? => true)).to example.have_failed
64+
end
65+
end
66+
67+
describe '#process' do
68+
before do
69+
allow(RSpec::Rails::MailboxExampleGroup).to receive(:create_inbound_email) do |attributes|
70+
mail = double('Mail::Message', attributes)
71+
double('InboundEmail', :mail => mail)
72+
end
73+
end
74+
75+
it 'sends mail to the mailbox' do
76+
expect {
77+
example.process(:to => ['[email protected]'])
78+
}.to change(::ApplicationMailbox, :received).by(1)
79+
end
80+
end
81+
end
82+
end
83+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require "spec_helper"
2+
require "rspec/rails/feature_check"
3+
4+
class ApplicationMailbox
5+
class Router
6+
def match_to_mailbox(*)
7+
Inbox
8+
end
9+
end
10+
11+
def self.router
12+
Router.new
13+
end
14+
end
15+
16+
class Inbox < ApplicationMailbox; end
17+
class Otherbox < ApplicationMailbox; end
18+
19+
RSpec.describe "ActionMailbox matchers", :skip => !RSpec::Rails::FeatureCheck.has_active_job? do
20+
include RSpec::Rails::Matchers::ActionMailbox
21+
22+
describe "receive_inbound_email" do
23+
let(:to) { ['[email protected]'] }
24+
25+
before do
26+
allow(RSpec::Rails::MailboxExampleGroup).to receive(:create_inbound_email) do |attributes|
27+
mail = double('Mail::Message', attributes)
28+
double('InboundEmail', :mail => mail)
29+
end
30+
end
31+
32+
it "passes when it receives inbound email" do
33+
expect(Inbox).to receive_inbound_email(:to => to)
34+
end
35+
36+
it "passes when negated when it doesn't receive inbound email" do
37+
expect(Otherbox).not_to receive_inbound_email(:to => to)
38+
end
39+
40+
it "fails when it doesn't receive inbound email" do
41+
expect {
42+
expect(Otherbox).to receive_inbound_email(:to => to)
43+
}.to raise_error(/expected mail to [email protected] to route to Otherbox, but routed to Inbox/)
44+
end
45+
46+
it "fails when negated when it receives inbound email" do
47+
expect {
48+
expect(Inbox).not_to receive_inbound_email(:to => to)
49+
}.to raise_error(/expected mail to [email protected] not to route to Inbox/)
50+
end
51+
end
52+
end

0 commit comments

Comments
 (0)