Feedback on matcher

We have moved from Rails 2 to 3 and the changing Mailer syntax has
caused us to rewrite some of our specs.

In Rails 2:

Implementation:
Mailer.deliver_job_application(job.id, user.id)

Spec:
Mailer.should_receive(:deliver_job_application).with(@job.id,
@user.id)


In Rails 3:

Implementation:
Mailer.job_application(job.id, user.id).deliver

Spec:
message = double
message.should_receive(:deliver)
Mailer.should_receive(:job_application).with(@job.id,
@user.id).and_return(message)


I turned the latter example into a matcher for RSpec 2 and I’m open
for feedback.

Here’s a gist incase the inline formatting sucks:

RSpec::Matchers.define :deliver do |message|
chain :with do |*args|
@with = args
end

match do |mailer|
mail = double
mail.should_receive(:deliver)

mailer.should_receive(message).with(*@with).and_return(mail)

end
end

Mailer.should deliver(:job_application).with(@job.id, @user.id)


Is this a sane approach? Would it have been better to adapt the
Mailer interface to comply with the specs?

Best,
Michael G.

On Jan 27, 2011, at 7:48 AM, Michael G. wrote:

Mailer.should_receive(:job_application).with(@job.id,
chain :with do |*args|

Mailer.should deliver(:job_application).with(@job.id, @user.id)


Is this a sane approach?

I think it’s sane for inside your own app, but not as part of a lib.
First, it’s bound to rspec-mocks, and including it in an rspec lib would
require extra handling to either make it only available for rspec-mocks
or make it support the other frameworks that rspec supports. Second, it
hides a message expectation. Again, that’s fine for your own app, in
which you know what’s going on, but would confuse some users if it were
in a lib.

Make sense?

Would it have been better to adapt the
Mailer interface to comply with the specs?

That’s a funny thing. There are a lot of things that I think would work
differently in Rails API’s if they were driven out with a TDD mindset.
But the API’s seem to be designed with rapid prototyping in mind more
than long term maintainability. For example, I’d really like to be able
to write a controller example like this:

not anything close to reality

controller = ThingsController.new
response = controller.action(:some => ‘params’)
response.should be_redirect_to(:action => ‘other’)

But we don’t really have a public API for instantiating controllers,
which is why we have the http verb methods, which conflate routing
concerns with controller concerns.

I’m not judging these choices as good or bad. Just observing one aspect
of the resulting ‘feel’.

FWIW,
David

On Thu, Jan 27, 2011 at 10:30 AM, David C. [email protected]
wrote:

Spec:
Here’s a gist incase the inline formatting sucks:
after.rb · GitHub
mailer.should_receive(message).with(*@with).and_return(mail)

Make sense?

Definitely, although I’m sure I’m not the only one verifying Mailers
in this way, it’d be nice to have a library.

But we don’t really have a public API for instantiating controllers, which is
why we have the http verb methods, which conflate routing concerns with controller
concerns.

This is certainly an interesting point that you bring up. I had never
really considering the “why” in why we write controller specs like we
do.

Thanks for your insight David.

Best,
Michael G.

On Thu, Jan 27, 2011 at 2:05 PM, Michael G. [email protected]
wrote:

Mailer.deliver_job_application(job.id, user.id)

I think it’s sane for inside your own app, but not as part of a lib. First,
it’s bound to rspec-mocks, and including it in an rspec lib would require extra
handling to either make it only available for rspec-mocks or make it support the
other frameworks that rspec supports. Second, it hides a message expectation.
Again, that’s fine for your own app, in which you know what’s going on, but would
confuse some users if it were in a lib.
Failure/Error: Mailer.should_not
deliver(:job_application).with(@job.id, @user.id)
expected Mailer not to deliver :candidate_abandon_message

Please ignore the fact that the failure message
(candidate_abandon_message) is incorrect, I was adjusting my examples
for consistency.

Best,
Michael G.

On Thu, Jan 27, 2011 at 10:30 AM, David C. [email protected]
wrote:

Spec:
Here’s a gist incase the inline formatting sucks:
after.rb · GitHub
mailer.should_receive(message).with(*@with).and_return(mail)

Make sense?

Once you confirmed my sanity, I started implementing this matching in
the rest of the project. Everything was fine until I came to an
example that negates the expectation with should_not.

Mailer.should_not deliver(:job_application).with(@job.id, @user.id)

Failure/Error: Mailer.should_not
deliver(:job_application).with(@job.id, @user.id)
expected Mailer not to deliver :candidate_abandon_message

I can fix this by adding another matcher:

RSpec::Matchers.define :not_deliver do |message|
match do |mailer|
mailer.should_not_receive(message).with(*@with)
end
end

but this feels awfully hacky and unconventional. I know Capybara has
to do something similar with has_content / has_no_content and I know
my team (and myself) never seem to get this correct.

Is there a better way to do this?

Best,
Michael G.

Hi,

On Thu, Jan 27, 2011 at 21:48, Michael G. [email protected] wrote:

end
end

Mailer.should deliver(:job_application).with(@job.id, @user.id)

On a recent Rails 3 project we implemented a matcher for sending email
but without using any stubs. It looks something like this:

RSpec::Matchers.define :send_mail do
match do |block|
ActionMailer::Base.deliveries = []
block.call
ActionMailer::Base.deliveries.count.should == 1
mail = ActionMailer::Base.deliveries.last
# now inspect the mail object as you see fit
end
end

Then in our spec we have:

expect { some_func }.to send_email

We also have chains for recipients, subject, content, and so on.

HTH,
Mike