You're not stubbing, stupid!

Patrickbaselier

Patrick Baselier - 17 April 2020
438 words in about 2 minutes

Recently, in a Ruby on Rails project, I was writing a Cucumber scenario that was deleting a resource by having the user clicking a ‘Destroy’ button. Before the action was executed, the user had to confirm a message shown in a confirmation dialog. You may have seen this dozens of times when scaffolding a Rails application.
Oh and upon deleting, I also had to do a request to an external API (to be more precise: the use case was that of a user unsubscribing, so I had to send a DELETE request to a Mollie API).

So, “nothing new here”, I thought. I knew about WebMock, since I wanted to stub the external API request and my test suite was set up to test JavaScript, so I knew I could use the accept_confirm method here that Capybara offers.

Stubbing the request was defined in a support file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# features/support/webmock.rb

require 'webmock/cucumber'

WebMock.disable_net_connect!(allow_localhost: true)

Before do |_scenario|
  stub_request(
    :delete, 
    %r{https://api.mollie.com/v2/customers/\w+/subscriptions/\w+}
  )
    .to_return(body: {}.to_json)
end

My step implementation looked like this:

1
2
3
4
5
# features/step_definitions/general_steps.rb

When('I delete the resource') do
  accept_confirm { click_on 'Destroy' }
end

The test failed! It was telling me that I should stub the DELETE request.

1
2
3
4
5
6
7
  Real HTTP connections are disabled. Unregistered request: DELETE https://api.mollie.com/v2/customers/...

  You can stub this request with the following snippet:

  stub_request(:delete, "https://api.mollie.com/v2/customers/...").
    ...
    to_return(status: 200, body: "", headers: {})

Wasn’t I doing this?

After many (!!!) hours of trying rewriting the stub, rubber ducking with colleagues, writing alternative scenarios, I decided to get rid of the confirmation dialog that was shown to the user. This way I didn’t need the accept_confirm and… tadaaa: it worked! My test was passing.

My theory was that accept_confirm executes in a different thread or something in which the stub is not defined (I know a theory can be proven wrong, but this one worked for me).
One way to work around this is, instead of using accept_confirm, is ‘overriding’ the JavaScript’s confirm function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# features/support/my_world.rb
module MyWorld
  def click_on_and_confirm(locator)
    link_or_button = find(:link_or_button, locator)
    page.evaluate_script('window.confirm = () => true')
    expect(link_or_button['data-confirm']).to be_a(String)
    link_or_button.click
  end
end

World(MyWorld)

# features/step_definitions/general_steps.rb

When('I delete the resource') do
  click_on_and_confirm 'Destroy'
end

This way the DELETE request is being stubbed, you can still show a nice confirmation dialog and your test will pass.

Be aware that during the test, the confirmation dialog will not be shown anymore (due to the ‘stubbed’ confirm function in JavaScript. Therefor, I added an assertion to check indirectly if the confirmation dialog will be shown (by using the data-confirm attribute added and used by Rails) when clicking the ‘Destroy’ link or button.

Patrickbaselier

Patrick Baselier

I’m a professional Ruby on Rails-, front-end- and unprofessional (that is: not professionally… yet) Ember developer from The Netherlands, I love sharing knowledge and one day I hope to be a more than a novice guitar player.

At Kabisa, privacy is of the greatest importance. We think it is important that the data our visitors leave behind is handled with care. For example, you will not find tracking cookies from third parties such as Facebook, Hotjar or Hubspot on our website. Only cookies from Google and Vimeo are used in order to improve the user experience of our visitors. These cookies also ensure that relevant advertisements are displayed. Read more about the use of cookies in our privacy statement.