From 8ffd337a59ce45ccb632171bb3080edbb86dc3e3 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 9 Jul 2013 13:10:23 +1000 Subject: [PATCH] Add "Wait for AJAX" post --- ...7-09-waiting-for-ajax-in-capybara.markdown | 44 ++++++ .../waiting-for-ajax-in-capybara/index.html | 70 ++++++++++ _site/atom.xml | 132 +++++------------- _site/blogography.html | 2 + _site/index.html | 43 ++++-- 5 files changed, 183 insertions(+), 108 deletions(-) create mode 100644 _posts/2013-07-09-waiting-for-ajax-in-capybara.markdown create mode 100644 _site/2013/07/waiting-for-ajax-in-capybara/index.html diff --git a/_posts/2013-07-09-waiting-for-ajax-in-capybara.markdown b/_posts/2013-07-09-waiting-for-ajax-in-capybara.markdown new file mode 100644 index 000000000..0ff5af3d4 --- /dev/null +++ b/_posts/2013-07-09-waiting-for-ajax-in-capybara.markdown @@ -0,0 +1,44 @@ +--- +wordpress_id: RB-342 +layout: post +title: Waiting for AJAX in Capybara +--- + +In Spree recently, we've been using more and more of [Spree's API](http://guides.spreecommerce.com/api) for the Backend component. This means that we've introduced more AJAX-powered features into the backend, which has lead to some interesting test failures. + +Some of these test failures are that the tests just aren't waiting long enough for an AJAX request to complete before checking for content on the page. Others are more ... bewildering: + +``` +F +An error occurred in an after hook + ActiveRecord::StatementInvalid: + SQLite3::BusyException: + database is locked: DELETE FROM "spree_activators"; + occurred at ...lib/sqlite3/statement.rb:108:in `step' +``` + +This error happens when an AJAX request is still being processed by the server, but the test finishes and Database Cleaner attempts to wipe the database. The server has locked the database until it's done what it needs to do, and during that lock Database Cleaner attempts to wipe all the data and can't. + +To fix this, we just needed to wait for all AJAX requests to complete. This means replacing `sleep` with magic numbers, like this: + +``` +sleep(2) +``` + +With this method: + +``` +def wait_for_ajax + counter = 0 + while page.execute_script("return $.active").to_i > 0 + counter += 1 + sleep(0.1) + raise "AJAX request took longer than 5 seconds." if counter >= 50 + end +end +``` + +This code will call `$.active` which is jQuery-code for "how many `$.ajax` requests are still active?", and if that returns more than 0, then it will sleep for a moment, and check again. This code gives AJAX requests 5 seconds to wrap up before raising an exception and moving on. + +Use this `wait_for_ajax` method when you need to wait for AJAX requests to finish in your tests to prevent weird, unpredictable JavaScript errors. + diff --git a/_site/2013/07/waiting-for-ajax-in-capybara/index.html b/_site/2013/07/waiting-for-ajax-in-capybara/index.html new file mode 100644 index 000000000..a3e0a51d7 --- /dev/null +++ b/_site/2013/07/waiting-for-ajax-in-capybara/index.html @@ -0,0 +1,70 @@ + + + + Blog of Ryan Bigg - Waiting for AJAX in Capybara + + + + +

The Life of a Radar

+
+
+
Waiting for AJAX in Capybara
+ 09 Jul 2013
+

In Spree recently, we've been using more and more of Spree's API for the Backend component. This means that we've introduced more AJAX-powered features into the backend, which has lead to some interesting test failures.

+ +

Some of these test failures are that the tests just aren't waiting long enough for an AJAX request to complete before checking for content on the page. Others are more ... bewildering:

+
F
+An error occurred in an after hook
+  ActiveRecord::StatementInvalid: 
+    SQLite3::BusyException:
+    database is locked: DELETE FROM "spree_activators";
+  occurred at ...lib/sqlite3/statement.rb:108:in `step'
+
+

This error happens when an AJAX request is still being processed by the server, but the test finishes and Database Cleaner attempts to wipe the database. The server has locked the database until it's done what it needs to do, and during that lock Database Cleaner attempts to wipe all the data and can't.

+ +

To fix this, we just needed to wait for all AJAX requests to complete. This means replacing sleep with magic numbers, like this:

+
sleep(2)
+
+

With this method:

+
def wait_for_ajax
+  counter = 0
+  while page.execute_script("return $.active").to_i > 0
+    counter += 1
+    sleep(0.1)
+    raise "AJAX request took longer than 5 seconds." if counter >= 50
+  end
+end
+
+

This code will call $.active which is jQuery-code for "how many $.ajax requests are still active?", and if that returns more than 0, then it will sleep for a moment, and check again. This code gives AJAX requests 5 seconds to wrap up before raising an exception and moving on.

+ +

Use this wait_for_ajax method when you need to wait for AJAX requests to finish in your tests to prevent weird, unpredictable JavaScript errors.

+ +
+
+
+ + + + + blog comments powered by Disqus + + + diff --git a/_site/atom.xml b/_site/atom.xml index 48b9ec17e..299d687d2 100644 --- a/_site/atom.xml +++ b/_site/atom.xml @@ -4,7 +4,7 @@ The Life of a Radar - 2013-06-26T14:32:23+10:00 + 2013-07-09T13:10:00+10:00 http://ryanbigg.com/ Ryan Bigg @@ -12,6 +12,42 @@ + + Waiting for AJAX in Capybara + + 2013-07-09T00:00:00+10:00 + http://ryanbigg.com/2013/07/waiting-for-ajax-in-capybara + In Spree recently, we've been using more and more of Spree's API for the Backend component. This means that we've introduced more AJAX-powered features into the backend, which has lead to some interesting test failures.

+ +

Some of these test failures are that the tests just aren't waiting long enough for an AJAX request to complete before checking for content on the page. Others are more ... bewildering:

+
F
+An error occurred in an after hook
+  ActiveRecord::StatementInvalid: 
+    SQLite3::BusyException:
+    database is locked: DELETE FROM "spree_activators";
+  occurred at ...lib/sqlite3/statement.rb:108:in `step'
+
+

This error happens when an AJAX request is still being processed by the server, but the test finishes and Database Cleaner attempts to wipe the database. The server has locked the database until it's done what it needs to do, and during that lock Database Cleaner attempts to wipe all the data and can't.

+ +

To fix this, we just needed to wait for all AJAX requests to complete. This means replacing sleep with magic numbers, like this:

+
sleep(2)
+
+

With this method:

+
def wait_for_ajax
+  counter = 0
+  while page.execute_script("return $.active").to_i > 0
+    counter += 1
+    sleep(0.1)
+    raise "AJAX request took longer than 5 seconds." if counter >= 50
+  end
+end
+
+

This code will call $.active which is jQuery-code for "how many $.ajax requests are still active?", and if that returns more than 0, then it will sleep for a moment, and check again. This code gives AJAX requests 5 seconds to wrap up before raising an exception and moving on.

+ +

Use this wait_for_ajax method when you need to wait for AJAX requests to finish in your tests to prevent weird, unpredictable JavaScript errors.

+]]>
+
+ Finding SQL queries in Rails @@ -1280,99 +1316,5 @@ the one that was one of the, if not the first, hosting company to offer ]]> - - Deciding what tests to write - - 2011-12-05T00:00:00+11:00 - http://ryanbigg.com/2011/12/deciding-what-tests-to-write - More people who are new to Ruby are getting into TDD and BDD thanks to the wonderful tools like RSpec and -Cucumber that make it easy for them to do so. There's no real public information on exactly what you should be testing, though.

- -

Testing Validations

- -

RSpec supports the shoulda-matchers gem and I see a lot of people using this to test validations on their models, -and this is the first test that they're writing. I personally think that this is the wrong way of doing things.

- -

The first test should be one that follows the exact steps that the user would need to do in order to approve this -functionality.

- -

In the example test (written in Capybara's syntax) below, the user fills in the form (ignoring one of the required fields) and then should see that there's an error on the page.

-
visit root_path
-click_link "Posts"
-click_link "New Post"
-
-fill_in "Title", :with => "Deciding what tests to write"
-click_button "Create Post"
-
-within("#flash_alert") do
-  page.should have_content("Post could not be created.")
-end
-
-within("#post_form .errors") do
-  page.should have_content("Body cannot be blank")
-end
-
-

Or if you prefer Cucumber:

-
Given a user is creating a new post
-And the user leaves the post text blank
-When the user clicks "Create Post"
-Then they should see a validation error:
-  """
-    Body cannot be blank
-  """
-
-

You can imagine the steps that Cucumber would use, they'd use the Capybara methods just like the original example.

- -

The point is, this test is testing the user's interaction with the page, which I think is the most critical part -of the application. If the user cannot see this error message, why does it matter if the model validates the -presence of this field? It doesn't.

- -

Testing that the model contains the validation is a secondary thing, and is generally something I skip doing. The -test for at least one of the form's validations goes into the request spec. If I'm feeling pedantic I'll write -another for the other validations, but I can assume that dynamic_form (the thing that provides -error_messages_for is doing the right thing when it comes to its own methods. If one error message is showing -up, good chance the others are.

- -

In the case where validation error messages don't show up then I write a specific test for that inside the -request spec before going on to fix it wherever I need to.

- -

Testing Complex Logic

- -

In Spree, there's complex logic involved around orders and tracking -inventory. This is something I could test with a request spec, but how the system works is made up of so many -little parts it makes it slow to test the whole thing:

- -
    -
  • A product exists in the system, has a "count on hand" of 1.
  • -
  • A user wants to buy this product, so clicks "Add to cart"
  • -
  • A new order is created in the system, with this item
  • -
  • User is prompted to sign in
  • -
  • User is prompted for billing + shipping details.
  • -
  • User is prompted for credit card details
  • -
  • User clicks "Confirm" on order page
  • -
  • Order goes through, deducting 1 from "count on hand" total, bringing it to 0.
  • -
- -

A huge portion of these steps aren't even required to test the count on hand decreasing. What we care about is -that when an order is placed in the system that the count on hand decreases by one. And so this is where a unit -test would be better.

- -

This unit test would check that when an order is created, a method such as unstock_items! is called. There would then be another unit test for the actual function of the unstock_items! -call, ensuring that it goes through the line items for the order and depletes the stock on the products as necessary.

- -

The unit test is going to be much lighter (and quicker to run!) than the request spec, which is just a massive win.

- -

Conclusion

- -

At the final retrospective at the CodeRetreat event on Saturday in Sydney, it was brought up that there's no "right" way to test. Some people thought that testing from the "bottom-up" (unit test first, request specs or similar later) was better, but then they saw the merits of testing "top-down" (request specs or similar first, then unit tests for the fiddly bits) as well.

- -

I think liberal applications of both of these methodologies is one "right" way to test. The more I test, the more I find myself getting better at knowing what to test and how to test it. I -can see the merits of both ways. I'm preferring top-down though, as that's, in my opinion, testing what the client is going to be seeing, and that's what matters most. If there's a bit of -gnarly code in there, like the order inventory tracking, then that's when I'd dive down into unit testing.

- -

What are your thoughts on this?

-]]>
-
- \ No newline at end of file diff --git a/_site/blogography.html b/_site/blogography.html index 27bcc95b4..7cf825dc7 100644 --- a/_site/blogography.html +++ b/_site/blogography.html @@ -29,6 +29,8 @@

The Blogography of a Radar

The formatting for earlier posts may be a little skewiff. If you find something like that, patches are very welcome.