I'm maintaining a suite of feature specs with lots of sidekiq jobs being run within tests. Everything was quite fine until there's been a need to enable javascript for all feature specs, and things suddenly got ugly. I'm not sure I understand reasons for that.
A typical problem looks like this: At the beginning of spec I invoke helper, that eventually starts Sidekiq job:
it 'does something fancy' do
Sidekiq::Testing.inline! do
page.attach_file('black_list_file', file)
click_on 'Import file'
end
expect(page).to have_content('Import started') # No problem here
expect(page).to have_link('imported_file.csv') # This fails
end
I tried putting sleep n
between expect
statements - no luck even with n = 10
. Then I tried to debug, and noticed something strange: ImportWorker.jobs
returns the job still in queue, and if I explicitly state ImportWorker.drain
from debugger, spec will pass.
But! If I put ImportWorker.drain
between expect
lines - it still fails, even if I put sleep
before drain, to wait for request to actually invoke perform_async
on worker.
And things get even more strange when I switch to selenium driver - everything goes smoothly, test passes.
Now, I have some insight into how requests made from capybara are processed in different thread from specs, as explained here. So probably, when worker is invoked, it has no idea about inline!
, and just puts the job into Redis queue. I still don't understand though why Selenium is capable of managing this issue. And if this is true, how do I test features, that involve sidekiq jobs, using webkit driver.
UPD.
Okay, as I expected Sidekiq::Testing.inline?
returns false at the place, where worker is called, even if request was made from inline!
block.
This solves all problems:
if Rails.env.test?
Sidekiq::Testing.inline! do
ImportWorker.perform_async(args)
end
else
ImportWorker.perform_async(args)
end
Obviously, this is as far away from best practices as possible, so I'm thinking of writing a class, that will accept worker name as a string or symbol, and call perform on it with or without inline!
block depending on env. This way I can keep this ugliness in one place, without the need to pollute controllers and models.
Please comment if you think this is a bad solution.
All Sidekiq::Testing.inline! does (when passed a block) is set a global (not thread specific) setting before executing the block and then reset that setting when the block finishes. I believe the reason your test is failing is that even though you're clicking the button/link inside the block the job actually isn't being added to the queue while the global inline setting is set. This is because #click_on
returns immediately after clicking and isn't required to wait for any side effects of that click to occur (form submission, JS behavior, etc). If you move your expects (which do wait for the side effects to occur) inside the block then the test should pass.
it 'does something fancy' do
Sidekiq::Testing.inline! do
page.attach_file('black_list_file', file)
click_on 'Import file'
expect(page).to have_content('Import started') # No problem here
expect(page).to have_link('imported_file.csv')
end
end
Another option would be to use an RSpec around block to change the Sidekiq testing setting based on metadata set on the test - add something like the following to your RSpec config (note: untried)
RSpec.configure do |config|
config.around(:example, :sidekiq_inline) do |example|
Sidekiq::Testing.inline! do
example.run
end
end
end
which should then let you do
it 'does something fancy', :sidekiq_inline do
page.attach_file('black_list_file', file)
click_on 'Import file'
expect(page).to have_content('Import started') # No problem here
expect(page).to have_link('imported_file.csv')
end
You could also add things like clearing the sidekiq queue to the around block too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With