Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rspec: How to use expect to receive with a resource which does not exist yet?

In an action called via a post request I'm creating a resource call RequestOffer and send an email with ActionMailer using the created resource as a parameter:

@request_offer = RequestOffer.new(request_offer_params)
if @request_offer.save
    RequestOfferMailer.email_team(@request_offer).deliver_later
end

When my controller spec, I want to test that my RequestOfferMailer is called using the method email_team with the resource @request_offer as a parameter.

When I want to user expect(XXX).to receive(YYY).with(ZZZ), the only way I found was to declare my expectation before making the POST request. However, ZZZ is created by this POST request, so I have no way to set my expectation before.

# Set expectation first
message_delivery = instance_double(ActionMailer::MessageDelivery)

# ZZZ used in .with() does not exist yet, so it won't work
expect(RequestOfferMailer).to receive(:email_team).with(ZZZ).and_return(message_delivery)
expect(message_delivery).to receive(:deliver_later)

# Make POST request that will create ZZZ
post :create, params

Any idea how to solve this problem?

like image 811
John Smith Avatar asked Sep 13 '25 05:09

John Smith


2 Answers

If this is a functional test then I would isolate the controller test from the DB. You can do this by using instance_doubles and let statements. Here's an example that you may like to extend for your purposes

describe '/request_offers [POST]' do
  let(:request_offer) { instance_double(RequestOffer, save: true) }

  before do
    allow(RequestOffer).to receive(:new).
      with(...params...).
      and_return(request_offer)
  end

  it 'should instantiate a RequestOffer with the params' do
    expect(RequestOffer).to receive(:new).
      with(...params...).
      and_return(request_offer)
    post '/request_offers', {...}
  end

  it 'should email the request offer via RequestOfferMailer' do
    mailer = instance_double(ActionMailer::MessageDelivery)

    expect(RequestOfferMailer).to receive(:email_team).
      with(request_offer).and_return(mailer)

    post '/request_offers', {...}
  end
end

The key to this is using 'let' to declare an instance double of the model that you intend to create. By setting expectations on the class you can inject your instance double into the test and isolate from the DB. Note that the 'allow' call in the before block is there to serve the later specs that set expectations on the mailer object; the 'expect' call in the first test will still be able to make assertions about the call.

like image 143
AndyV Avatar answered Sep 14 '25 20:09

AndyV


The last argument of the with method is a block. You can open up the arguments and do anything you like there.

expect(RequestOfferMailer)
  .to receive(:email_team)
  .with(instance_of(RequestOffer)) do |request_offer|
    expect(request_offer.total).to eq(100) # As one example of what you can to in this block 
  end.and_return(message_delivery)    

You can also set the instance_of matcher to be anything if you're not even sure what object type you're expecting.

like image 43
toxaq Avatar answered Sep 14 '25 20:09

toxaq