How would you rewrite this test for instantiating a new class?

I have an existing method for a Post model that looks like this:

def send_update_email
  PostEmail.deliver(self)
end

with the corresponding, passing rspec test:

context 'send_update_email' do
  it 'delegates to PostEmail' do
    post = create_post #separate method that uses factory girl
    expect(PostEmail).to receive(:deliver).with(post)
    post.send_update_email
  end
end

The PostEmail class has this class method:

def self.deliver(post)
  new(post).deliver
end

This looks to me like it’s just initiating the instance and then calling the ‘deliver’ instance method.

I would like to remove that class method in PostEmail so I can rewrite the original method like this:

def send_update_email
  PostEmail.new(self).deliver
end

but I can’t figure out how to rewrite the test to get it to pass.

I don’t really understand how the existing test is working since wouldn’t you usually would use the ‘receive’ method on a mock? In this case it’s just using it on PostEmail.
Any ideas on what I should do here to get this refactoring to work?

You can build a double that stands in for the PostEmail instance, and then stub PostEmail so that .new returns the double rather than a PostEmail:

it 'delivers an email with the post' do
  post_email_double = double('post email double', deliver: true)
  allow(PostEmail).to receive(:new) { post_email_double }
  post = create_post

  post.send_update_email

  expect(PostEmail).to have_received(:new).with(post) # want to verify the post was used
  expect(post_email_double).to have_received(:deliver) # want to verify that deliver was called
end

I like putting the expectations at the end rather than before the test so that I can keep the test flow in setup, exercise, then verification in that order.

Hope this helps.

Excellent, thank you Geoff!
So putting the expectations at the end is why you would use ‘have_received’ instead of ‘receive’, is that right?

That’s correct. In order to do this in RSpec you used to have to use a gem like Bourne, but the functionality was merged into RSpec 2 at some point last year. I think having the expectations always fall at the end makes reading tests easier, as you never have to check back for expectations earlier than the test exercise.

Yes, that makes sense. Thanks!