when I send a mail through deliver_later
it is managed by sidekiq, and then my registered mail observer is triggered.
I have a Capybara test that checks states changed inside observer code, but it fails randomly if observer is not executed right after the clicks, and the expectation doesn't work correctly.
Example:
# spec
scenario 'Test that fails randomly' do
click_link "Go!"
# MyModel#done is a boolean attribute, so we have #done? method availiable
expect(MyModel.first.done?).to be true
end
# The controller that manages the Go! link, triggers a mailer.
# After the mailer, this is executed.
# Registered observer
def delivered_mail(mail)
email = Email.find_by_message_id mail.message_id
email.user.update_attributes done: true
end
Fun fact: If I execute this scenario isolated, the test will always pass. If I execute the test suite completely, the test will 9:1 fail:pass more or less. ¯_(ツ)_/¯
Tried putting this in rails_helper:
require 'sidekiq/testing'
RSpec.configure do |config|
Sidekiq::Testing.inline!
end
And also putting Sidekiq::Testing.inline!
in the very first line of the scenario
block... nothing. The same fun fact.
Added database_cleaner
gem, and now it fails everytime.
Actions in Capybara (click_link
, etc) know nothing about any behaviors they trigger. Because of this there is no guarantee as to what the app will have done after your click_link
line returns, other than the link will have been clicked, and the browser will have started to perform whatever that action triggers. Your test then immediately checks 'MyModel.first.done?` while the browser could still be submitting a request (This is one reason why directly checking database records in feature tests is generally frowned upon).
The solution to that (and end up with tests that will work across multiple drivers reliably is to check for a visual change on the page that indicates the action has completed. You also need to set up ActiveJob properly for testing so you can make sure the jobs are executed. To do this you will need to include ActiveJob::TestHelper
, which can be done in your RSpec config or individual scenarios, and you will need to make sure ActiveJob::Base.queue_adapter = :test
is set (can be done in config/environment/tests.rb file if wanted). Then assuming your app shows a message "Mail sent!" on screen when the action has completed you would do
include ActiveJob::TestHelper
ActiveJob::Base.queue_adapater = :test
...
perform_enqueued_jobs do
click_link "Go!"
expect(page).to have_text('Mail sent!') # This will wait for the message to appear, which guarantees the action has completed and enqueued the job
end # when this returns any jobs enqueued during the block will have been executed
expect(MyModel.first.done?).to be true