1

I have this spec:

  it 'syncs active campaign contact on user update' do
    expect(Marketing::ActiveCampaign::SyncContact).to receive(:run)

    login(@user, then_visit: "/account/#{@user.id}/settings")

    fill_in('customer[cc_email_address]', with: 'mean@google.com')
    find('.user-submit').click
    # this does a submit to the server which will result in
    # SyncContact.run being called
    # a spinner is put up while waiting for the post to 
    # complete
  end

how can I have the spec wait the minimum amount of time so that the expect to receive mock has a chance to receive the message?

I can obviously put in a dead wait after the find, which I hate to do.

I can also do something like this at the end of the spec

    expect(page).to have_css('.loading-image-div')
    expect(page).to have_no_css('.loading-image-div')

but I don't like this either as the first check may fail if the response from the server is very fast.

What I want is to just have the spec keep waiting (up to max wait time) for the message to be receive, and then only then fail.

Mitch VanDuyn
  • 2,838
  • 1
  • 22
  • 29

2 Answers2

3

Yes, you can use the sleep method to wait for a specified time by passing the value in a second.

Using the sleep method in your tests is not recommended because it can make your tests slower and less reliable. Instead, you can use the wait method to wait before continuing with the test.

example

expect { perform_method }.to change { old_data }.from(nil).to(new_data).wait(2.seconds)

Stub sleep with RSpec

2

after some thought the correct solution is to realize that the spec needs to wait for the message (first line in the spec) to be received on the server before proceeding.

Simplistically this can be done like this:

it 'syncs active campaign contact on user update' do
  expect(Marketing::ActiveCampaign::SyncContact).to receive(:run) do
    @contact_synced = true
  end

  login(@user, then_visit: "/account/#{@user.id}/settings")

  fill_in('customer[cc_email_address]', with: 'mean@google.com')
  find('.user-submit').click
  wait_for { @contact_synced }.to be_truthy
end

This uses the wait_for gem https://github.com/laserlemon/rspec-wait. In our actual case we can check the state of the user:

wait_for { @user.reload.active_campaign_id }.to be_present

but to make this work we do need to pass the original message on to the service object:

expect(Marketing::ActiveCampaign::SyncContact).to receive(:run).and_call_original

finally, the spec can check the spinners mentioned as an added niceness

  it 'syncs active campaign contact on user update' do
    wait_for(Marketing::ActiveCampaign::SyncContact).to receive(:run).and_call_original do
      # make sure we put up a spinner while syncing the contact
      expect(page).to have_css('.loading-image-div')
    end

    login(@user, then_visit: "/account/#{@user.id}/settings")

    fill_in('customer[cc_email_address]', with: 'mean@google.com')
    find('.user-submit').click
    # should have set the active_campaign id
    wait_for { @user.reload.active_campaign_id }.to be_present
    # and removed the spinner
    expect(page).to have_no_css('.loading-image-div')
  end
end

The answers I received was helpful as well as the comment in realizing this so I have upvoted them. Thanks

Mitch VanDuyn
  • 2,838
  • 1
  • 22
  • 29
  • I did. Thanks for the feedback on HyperStack. We certainly feel that we could not have accomplished our business goals without it. – Mitch VanDuyn Mar 09 '23 at 13:33