Incerteza
Incerteza

Reputation: 34884

Rspec fails on "let"s

I have a mailer

class MyMailer < ActionMailer::Base
  def send1(a, b, c)
    #.....
    mail(....).deliver
  end

  def send2(a, b, c)
    #.....
    mail(....).deliver
  end
end

And I test it

describe MyMailer do
  describe "#send1" do
    let(:a) { "[email protected]" }
    let(:b) { "message" }
    let(:c) { FactoryGirl.create :user }
    let(:send1_email1) { MyMailer.send1 a, b, c }

    after(:all) { MyMailer.deliveries.clear }

    it "should send an email" do
      expect(MyMailer.deliveries.count).to eq(1)
    end

    #........ other ITs testing send1_emai1

  describe "#send2" do
    let(:d) { "[email protected]" }
    let(:e) { FactoryGirl.create :admin }
    let(:g) { "message2" }
    let(:send1_emai2) { MyMailer.send2 d, e, g }

    after(:all) { MyMailer.deliveries.clear }

    it "should send an email2" do
      expect(MyMailer.deliveries.count).to eq(1)
    end

    #........ other ITs testing send1_emai2

end

So most of the time it fails due to the errors should send an email expect 1 got 2 and should send an email expect 1 got 4 or 0 Sometimes the numbers are different (got 0 - 4). I tried to do this:

  let!(:send1_email) { MyMailer.send1 a, b, c }
  let!(:send1_emai2) { MyMailer.send2 d, e, g }

But it also failed.

Failure/Error: expect(MyMailer.deliveries.count).to eq(1)

       expected: 1
            got: 4

I wonder why?

Previously I used before :all and @variables and everything was well (no errors)

The same errors:

   it "should send an email" do
      expect(MyMailer.deliveries.count).to eq(1)
      MyMailer.deliveries.clear
    end

   it "should send an email2" do
     expect(MyMailer.deliveries.count).to eq(1)
     MyMailer.deliveries.clear
   end

Upvotes: 1

Views: 131

Answers (2)

Benjamin Bouchet
Benjamin Bouchet

Reputation: 13181

There is a difference between let and let!

let is instantiated only when called, in other words, rspec won't be looking inside let block before you use the variable.

let(:send1_email2) { MyMailer.send2 d, e, g }
it 'testing' do
  # email not sent yet because we use let without bang
  expect(MyMailer.deliveries.count).to eq(0)

  send1_email2.call_a_method
  # now email is sent since we used the variable send1_email2
  expect(MyMailer.deliveries.count).to eq(1)
end

let! is instantiated as if it was a before block

let!(:send1_email2) { MyMailer.send2 d, e, g }
it 'testing' do
  # before we do anything the email is sent because we used let with a bang
  expect(MyMailer.deliveries.count).to eq(1)
end

Additionaly do no user after(:all) { MyMailer.deliveries.clear }, but after { MyMailer.deliveries.clear } instead, otherwise your counter won't be cleared between your examples

Upvotes: 2

Tanel Suurhans
Tanel Suurhans

Reputation: 1761

As the documentation states at this location.

Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples.

Also i'm sure you are aware that rspec runs examples in random order, meaning other examples call the mail-sending let() helper, meaning by the time it gets to your test, the suite has already sent out 4 emails, making your test fail. You should not rely on the order of defining tests. You should be making sure that your test data is scrubbed and cleaned before your examples.

Upvotes: 2

Related Questions