0

I try to test a model. After the model under test is created, it creates an object from another model and sends out an email based on the other object's ID.

# model under test

class AccountabilityBuddy < ApplicationRecord
  after_save_commit :send_consent_inquiry

  def send_consent_inquiry
    consent_tracker = BuddyConsent.create!(accountability_buddy_id: self.id)
    BuddyMailer.with(buddy: self, consent_link: consent_tracker.id).buddy_request.deliver_later
  end
...

This is what I've tried so far:

# the model test

RSpec.describe AccountabilityBuddy, type: :model do
  subject(:buddy) { build(:accountability_buddy) }
  
    describe 'after_save callback' do
      let(:saving) do
        buddy.save!
        buddy.reload
      end

      it 'sends an email to the buddy' do
        # how that works: https://stackoverflow.com/questions/22988968/testing-after-commit-with-rspec-and-mocking/30901628#comment43748375_22989816
        expect { saving }
          .to have_enqueued_job(ActionMailer::MailDeliveryJob)
                .with('BuddyMailer', 'buddy_request', 'deliver_now', params: {buddy: buddy, consent_link: BuddyConsent.last.id }, args: [])
        # Test fails, BuddyConsent.last.id is nil
      end

      it 'creates a new BuddyConsent' do
        expect { saving }.to(change { BuddyConsent.all.count }.from(0).to(1))
        # Test passes
      end
    end
...

The first test fails because the BuddyConsent.last.id is nil. I know that BuddyConsent is generated correctly, because the ID is available inside the let block:

      let(:saving) do
        buddy.save!
        buddy.reload
        BuddyConsent.last.id # => "8be42055-112c-4ba6-bd1b-61b73946fb6e"
      end

How do I make it available inside the matcher and why is it out of context?

Rich Steinmetz
  • 1,020
  • 13
  • 28
  • 1
    Have you tried `buddy.run_callbacks :save`? I've never tried it in a block, but in the `it` it works for me. – sam Feb 21 '21 at 14:55
  • 1
    It is likely (and I would have to confirm) out of context because the block form of expect is run after the line is evaluated. Your intent is `call block (save) -> build expectation -> evaluate` but what is happening is `build expectation -> call block (save) -> evaluate`. You have clearly shown in the second example why this would be the case barbecue you want to know that "count" went from 0 to 1. This expectation needs to be know before the block executes so that the evaluation can determine the count prior to the block and after execution. – engineersmnky Feb 22 '21 at 18:58
  • @engineersmnky that's exactly the issue, thanks for pointing out! As far as I can see, this is an indication of a design flaw and a general issue with testing callbacks that trigger some kind of side effects. I've already started to refactor the code to account for that. – Rich Steinmetz Mar 04 '21 at 20:34

0 Answers0