ascherman
ascherman

Reputation: 1832

Rspec: model not updated when submitting form with js

I have a paper model. Which has two states: draft and approved.

I have a form on edit_paper_path which makes a put with remote: true.

My controller:

def update
  paper = Paper.find(params[:id])
  puts paper.status # => :draft
  paper.approved!
  puts paper.status # => :approved
end

My test is:

it 'changes status to Approved', js: true do
  expect {
    click_button 'Approve'
  }.to change { paper.status }
end

But the test fails, and I noticed that the change done in the controller on the model, is lost, so the status keeps being :draft.

Additional:

This is my database_cleaner config:

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.append_after(:each) do
    DatabaseCleaner.clean
  end
end

Any ideas?

Upvotes: 2

Views: 659

Answers (2)

max
max

Reputation: 102443

Your test is failing since the controller updates the record in the database while your test is operating on the model held in memory.

What you need to do is sync the representation in memory with the DB:

it 'changes status to Approved' do
  expect(paper.approved?).to be_falsy # sanity check
  click_button 'Approve'
  # trick to get capybara to wait for request to finish 
  # note that there actually needs to be a flash message
  expect(page).to have_css(".flash") 
  paper.reload
  expect(paper.approved?).to be_truthy
end

Edited with feedback from Anthony E to deal with race conditions.

Upvotes: 2

Anthony E
Anthony E

Reputation: 11245

Capybara AJAX requests run asynchronously, so paper.status is being evaluated before the value is updated in the database.

In fact, Capybara is decoupled from the database, so you should try to test your ajax update by querying the DOM itself using the typical has_content/has_css query methods Capybara provides.

You could fix this by using sleep to wait for the transaction to finish, but that's a bit of a hacky solution and isn't guaranteed to pass depending on how long it takes for the database commit to take place.

Another option is wait until the AJAX call completes by using a jQuery.active query script. There's a good article that explains this approach here: https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara.

Upvotes: 2

Related Questions