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: https://gist.github.com/798513 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 Guterl
On Jan 27, 2011, at 7:48 AM, Michael Guterl wrote:> 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: https://gist.github.com/798513 > > 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?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 Chelimsky <dchelimsky at gmail.com> wrote:> > On Jan 27, 2011, at 7:48 AM, Michael Guterl wrote: > >> 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: https://gist.github.com/798513 >> >> 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? > > 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? >Definitely, although I''m sure I''m not the only one verifying Mailers in this way, it''d be nice to have a library.>> 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. >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 Guterl
On Thu, Jan 27, 2011 at 10:30 AM, David Chelimsky <dchelimsky at gmail.com> wrote:> > On Jan 27, 2011, at 7:48 AM, Michael Guterl wrote: > >> 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: https://gist.github.com/798513 >> >> 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? > > 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? >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 Guterl
On Thu, Jan 27, 2011 at 2:05 PM, Michael Guterl <mguterl at gmail.com> wrote:> On Thu, Jan 27, 2011 at 10:30 AM, David Chelimsky <dchelimsky at gmail.com> wrote: >> >> On Jan 27, 2011, at 7:48 AM, Michael Guterl wrote: >> >>> 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: https://gist.github.com/798513 >>> >>> 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? >> >> 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? >> > > 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 >Please ignore the fact that the failure message (candidate_abandon_message) is incorrect, I was adjusting my examples for consistency. Best, Michael Guterl
Hi, On Thu, Jan 27, 2011 at 21:48, Michael Guterl <mguterl at gmail.com> wrote:> 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)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