5

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.

Community
  • 1
  • 1
Roman
  • 389
  • 1
  • 13

1 Answers1

5

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.

Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78