Блог Половнёва

RSpec & Rails: как проверить deliver_later(wait_until: ...)

Ситуация: клиенты регистрируются, а через три дня получают письмо с онбордингом. Скажем, таким кодом:

after_action :send_onboarding_email, only: :create

def send_onboarding_email
  ClientsMailer
    .with(recipient: user)
    .onboarding_email
    .deliver_later(wait_until: 3.days.from_now)
end

Чтобы убедиться, что письмо уйдет точно через три дня, есть два способа:

1. Цепочка из моков-стабов:

mailer = instance_double(ClientsMailer)
email = double(:onboarding_email, deliver_later: true)

allow(ClientsMailer)
  .to receive(:with).with(recipient: user)
  .and_return(email)

# ...

expect(email)
  .to have_received(:deliver_later)
  .with(wait_until: 3.days.from_now)

2. Встроенный матчер и тестовый адаптер для ActiveJob из rspec-rails:

# Это, конечно, лучше спрятать в хелперы
def with_test_active_job_adapter
  last_adapter = ActiveJob::Base.queue_adapter
  ActiveJob::Base.queue_adapter = :test

  yield

  ActiveJob::Base.queue_adapter = last_adapter
end

around do |example|
  with_test_active_job_adapter { example.run }
end

it "schedules onboarding email in 3 days from now" do
  expect { ... }
    .to have_enqueued_job(ActionMailer::MailDeliveryJob)
    .with("ClientsMailer", "onboarding_email", "deliver_now")
    .at(3.days.from_now)
end

P. S. Использовать 3.days.from_now без Таймкопа опасно: могут быть ложноотрицательные тесты.

P. S. Ещё больше постов о программировании, тестах и культуре разработки у меня в Телеграме.