← Back to Upcase

Lesser of 2 evils in capybara? Sleep 1 or?


(Justin Gordon) #1

Anybody ever run into a Capybara test that just needed a sleep? I.e., the built in Capybara handling of asynch JS code just didn’t work.

Suppose that a you have a Capybara UI test that ensure some state. You click on a button, that runs some JavaScript code that results in another callback to add the property disabled=“disabled” to a radio button.

When the button is clicked, the UI first appears for a microsecond without the disabled="disabled"property, and then the callback can change that runs. I had a bug in this code, and I wanted to demonstrate the fix with a failing test. However, the test would pass because I would be asserting that the input does not contain disabled, which it doesn’t at first.

I could work around this issue by either:

  1. Putting in a sleep 1
  2. Starting the bit of UI being displayed to have some OK button with state disabled, and then enabling that button at the end of the 2nd callback (which I’m testing). Seems reasonable to have the button disabled until the code finishes.

I hate putting in a sleep, and I hate changing the code for a test. And neither of these terribly bad either.

Any opinions?


(Matthew Sumner) #2

So there is a thoughtbot blog post about waiting for ajax calls to finish. Could be what your looking for?

Personally I would be tempted to sleep 1. If there are a lot of these situations then that’s not a sustainable option.


(Derek Prior) #3

No Sleep. Never.

Try to find something user observable that you can wait for with a capybara selector. If there’s truly nothing, then the previously referenced post is a possibility, but I’ve never had a need for it. I find that with most things you end up having some user observable side effect.


(Justin Gordon) #4

Here’s my problem/solution writeup. Comments?

I don’t think jasmine would really have helped here as much as having the capybara test.

Problem

Using handlebars to load a dialog in client side CoffeeScript into a Bootstrap 3
"modal" dialog and having logic in the shown.bs.modal callback. If the thing
you are testing is that this callback does not do something (like disable a
component), how can you test that? The problem is that the test will incorrectly
pass, as the Capybara test will see the page before the callback runs.

Technique to Fix

  • Add class js-spec-something-descriptive at the end of the callback that you
    want to test:
$(".some-selector").addClass("js-spec-something-descriptive");
  • Put in this line of code before your assertion that the callback did
    something.
expect(page).to have_selector(".some-selector.js-spec-something-descriptive")

The whole skeleton of displaying a handlebars dialog looks like this, complete
with the last line that signifies that the callback finished.

    template = HandlebarsTemplates['some-template'](context);
    that = this
    $("#js-custom-modal-container").html(template)
    $dialogContent = $("#js-custom-modal")
    $dialogContent.modal('show')
    $dialogContent.on 'shown.bs.modal', (e) =>
      # some logic that needs testing
      $(".modal-content").addClass("js-spec-modal-content-loaded")

Yes, it’s not ideal to have to modify the production code for the test, but this
is far better than introducing sleeps in your tests.

Here’s a couple great articles on this topic of asynchronous testing:


(Derek Prior) #5

I don’t really understand what you’re getting at. CoffeeScript, Bootstrap - they’re all unrelated to the matter at hand here. When testing something with capybara, you’re not testing a callback - you’re testing behavior. What’s the behavior? From what I can gather it’s this:

  1. User is on a page with several components.
  2. User clicks a button that opens a modal that offers to either:
    A. Disable the components (presumably adding disabled attribute?)
    B. Cancel and do nothing.

Testing A seems straight forward. Check that after clicking the “disabled” property is set.

Testing B isn’t particularly interesting from a behavior standpoint. It seems like testing a form reset button. Adding the class to a selector that is meant to indicate that the user decided to cancel that modal seems kind of pointless. I’d probably trust that the cancel button on a modal, well, cancels. If I developed the modal myself or for some reason could not trust that, I’d probably unit test the modal with Jasmine or similar.


(Justin Gordon) #6

The issue was to have a failing test before changing the code. Since the Capybara would get a false positive before the callback ran, it was impossible to get the callback to run first without changing something in the DOM.