Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add "Wait for AJAX" post
  • Loading branch information
radar committed Jul 9, 2013
1 parent 6d57028 commit 8ffd337
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 108 deletions.
44 changes: 44 additions & 0 deletions _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.

70 changes: 70 additions & 0 deletions _site/2013/07/waiting-for-ajax-in-capybara/index.html
@@ -0,0 +1,70 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Blog of Ryan Bigg - Waiting for AJAX in Capybara</title>
<link href="http://feeds.feedburner.com/ryanbigg" rel="alternate" title="The Life of a Radar" type="application/atom+xml" />
<link rel='stylesheet' href='/css/style.css' media='screen'>
<link rel='stylesheet' href='/css/mobile.css'>
<body>
<h1 align='center'><a href='http://ryanbigg.com'>The Life of a Radar</a></h1>
<div id='page'>
<article>
<a href="/2013/07/waiting-for-ajax-in-capybara"><header>Waiting for AJAX in Capybara</header></a>
<small>09 Jul 2013</small><br>
<p>In Spree recently, we&#39;ve been using more and more of <a href="http://guides.spreecommerce.com/api">Spree&#39;s API</a> for the Backend component. This means that we&#39;ve introduced more AJAX-powered features into the backend, which has lead to some interesting test failures.</p>

<p>Some of these test failures are that the tests just aren&#39;t waiting long enough for an AJAX request to complete before checking for content on the page. Others are more ... bewildering:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">F
An error occurred in an after hook
ActiveRecord::StatementInvalid:
SQLite3::BusyException:
database is locked: DELETE FROM &quot;spree_activators&quot;;
occurred at ...lib/sqlite3/statement.rb:108:in `step&#39;
</code></pre></div>
<p>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&#39;s done what it needs to do, and during that lock Database Cleaner attempts to wipe all the data and can&#39;t.</p>

<p>To fix this, we just needed to wait for all AJAX requests to complete. This means replacing <code>sleep</code> with magic numbers, like this:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">sleep(2)
</code></pre></div>
<p>With this method:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">def wait_for_ajax
counter = 0
while page.execute_script(&quot;return $.active&quot;).to_i &gt; 0
counter += 1
sleep(0.1)
raise &quot;AJAX request took longer than 5 seconds.&quot; if counter &gt;= 50
end
end
</code></pre></div>
<p>This code will call <code>$.active</code> which is jQuery-code for &quot;how many <code>$.ajax</code> requests are still active?&quot;, 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.</p>

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

</article>
</div>
<div id='disqus_thread'></div>
<script type="text/javascript">
var disqus_shortname = 'ryanbigg'; // required: replace example with your forum shortname

var disqus_identifier = 'RB-342 http://ryanbigg.com/?p=RB-342'
var disqus_url = 'http://ryanbigg.com/2013/07/waiting-for-ajax-in-capybara';
</script>
<script src='http://ryanbigg.disqus.com/embed.js'></script>

<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
<script type="text/javascript">
var _gauges = _gauges || [];
(function() {
var t = document.createElement('script');
t.type = 'text/javascript';
t.async = true;
t.id = 'gauges-tracker';
t.setAttribute('data-site-id', '4e30f771f5a1f547c8000001');
t.src = '//secure.gaug.es/track.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(t, s);
})();
</script>
</body>
</html>
132 changes: 37 additions & 95 deletions _site/atom.xml
Expand Up @@ -4,14 +4,50 @@
<title>The Life of a Radar</title> <title>The Life of a Radar</title>
<link href="http://ryanbigg.com/atom.xml" rel="self"/> <link href="http://ryanbigg.com/atom.xml" rel="self"/>
<link href="http://ryanbigg.com"/> <link href="http://ryanbigg.com"/>
<updated>2013-06-26T14:32:23+10:00</updated> <updated>2013-07-09T13:10:00+10:00</updated>
<id>http://ryanbigg.com/</id> <id>http://ryanbigg.com/</id>
<author> <author>
<name>Ryan Bigg</name> <name>Ryan Bigg</name>
<email>radarlistener@gmail.com</email> <email>radarlistener@gmail.com</email>
</author> </author>




<entry>
<title>Waiting for AJAX in Capybara</title>
<link href="http://ryanbigg.com/2013/07/waiting-for-ajax-in-capybara"/>
<updated>2013-07-09T00:00:00+10:00</updated>
<id>http://ryanbigg.com/2013/07/waiting-for-ajax-in-capybara</id>
<content type="html"><![CDATA[<p>In Spree recently, we&#39;ve been using more and more of <a href="http://guides.spreecommerce.com/api">Spree&#39;s API</a> for the Backend component. This means that we&#39;ve introduced more AJAX-powered features into the backend, which has lead to some interesting test failures.</p>
<p>Some of these test failures are that the tests just aren&#39;t waiting long enough for an AJAX request to complete before checking for content on the page. Others are more ... bewildering:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">F
An error occurred in an after hook
ActiveRecord::StatementInvalid:
SQLite3::BusyException:
database is locked: DELETE FROM &quot;spree_activators&quot;;
occurred at ...lib/sqlite3/statement.rb:108:in `step&#39;
</code></pre></div>
<p>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&#39;s done what it needs to do, and during that lock Database Cleaner attempts to wipe all the data and can&#39;t.</p>
<p>To fix this, we just needed to wait for all AJAX requests to complete. This means replacing <code>sleep</code> with magic numbers, like this:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">sleep(2)
</code></pre></div>
<p>With this method:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">def wait_for_ajax
counter = 0
while page.execute_script(&quot;return $.active&quot;).to_i &gt; 0
counter += 1
sleep(0.1)
raise &quot;AJAX request took longer than 5 seconds.&quot; if counter &gt;= 50
end
end
</code></pre></div>
<p>This code will call <code>$.active</code> which is jQuery-code for &quot;how many <code>$.ajax</code> requests are still active?&quot;, 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.</p>
<p>Use this <code>wait_for_ajax</code> method when you need to wait for AJAX requests to finish in your tests to prevent weird, unpredictable JavaScript errors.</p>
]]></content>
</entry>

<entry> <entry>
<title>Finding SQL queries in Rails</title> <title>Finding SQL queries in Rails</title>
<link href="http://ryanbigg.com/2013/06/finding-sql-queries-in-rails"/> <link href="http://ryanbigg.com/2013/06/finding-sql-queries-in-rails"/>
Expand Down Expand Up @@ -1280,99 +1316,5 @@ the one that was one of the, if not <em>the</em> first, hosting company to offer
]]></content> ]]></content>
</entry> </entry>


<entry>
<title>Deciding what tests to write</title>
<link href="http://ryanbigg.com/2011/12/deciding-what-tests-to-write"/>
<updated>2011-12-05T00:00:00+11:00</updated>
<id>http://ryanbigg.com/2011/12/deciding-what-tests-to-write</id>
<content type="html"><![CDATA[<p>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&#39;s no real public information on exactly <em>what</em> you should be testing, though.</p>
<h3>Testing Validations</h3>
<p>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 <em>first</em> test that they&#39;re writing. I personally think that this is the wrong way of doing things.</p>
<p>The first test should be one that follows the exact steps that the user would need to do in order to approve this
functionality.</p>
<p>In the example test (written in Capybara&#39;s syntax) below, the user fills in the form (ignoring one of the required fields) and then should see that there&#39;s an error on the page.</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">visit root_path
click_link &quot;Posts&quot;
click_link &quot;New Post&quot;
fill_in &quot;Title&quot;, :with =&gt; &quot;Deciding what tests to write&quot;
click_button &quot;Create Post&quot;
within(&quot;#flash_alert&quot;) do
page.should have_content(&quot;Post could not be created.&quot;)
end
within(&quot;#post_form .errors&quot;) do
page.should have_content(&quot;Body cannot be blank&quot;)
end
</code></pre></div>
<p>Or if you prefer Cucumber:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">Given a user is creating a new post
And the user leaves the post text blank
When the user clicks &quot;Create Post&quot;
Then they should see a validation error:
&quot;&quot;&quot;
Body cannot be blank
&quot;&quot;&quot;
</code></pre></div>
<p>You can imagine the steps that Cucumber would use, they&#39;d use the Capybara methods just like the original example.</p>
<p>The point is, this test is testing the user&#39;s interaction with the page, which I think is the most <em>critical</em> 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? <em>It doesn&#39;t.</em></p>
<p>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&#39;s validations goes into the request spec. If I&#39;m feeling pedantic I&#39;ll write
another for the other validations, but I can <em>assume</em> that dynamic_form (the thing that provides
<code>error_messages_for</code> is doing the right thing when it comes to its own methods. If one error message is showing
up, good chance the others are.</p>
<p>In the case where validation error messages <em>don&#39;t</em> show up then I write a specific test for that inside the
request spec before going on to fix it wherever I need to.</p>
<h3>Testing Complex Logic</h3>
<p>In <a href='http://spreecommerce.com'>Spree</a>, there&#39;s complex logic involved around orders and tracking
inventory. This is something I <em>could</em> 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:</p>
<ul>
<li>A product exists in the system, has a &quot;count on hand&quot; of 1.</li>
<li>A user wants to buy this product, so clicks &quot;Add to cart&quot;</li>
<li>A new order is created in the system, with this item</li>
<li>User is prompted to sign in</li>
<li>User is prompted for billing + shipping details.</li>
<li>User is prompted for credit card details</li>
<li>User clicks &quot;Confirm&quot; on order page</li>
<li>Order goes through, deducting 1 from &quot;count on hand&quot; total, bringing it to 0.</li>
</ul>
<p>A huge portion of these steps aren&#39;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.</p>
<p>This unit test would check that when an order is created, a method such as <code>unstock_items!</code> is called. There would then be another unit test for the actual function of the <code>unstock_items!</code>
call, ensuring that it goes through the line items for the order and depletes the stock on the products as necessary.</p>
<p>The unit test is going to be much lighter (and quicker to run!) than the request spec, which is just a massive win.</p>
<h3>Conclusion</h3>
<p>At the final retrospective at the CodeRetreat event on Saturday in Sydney, it was brought up that there&#39;s no &quot;right&quot; way to test. Some people thought that testing from the &quot;bottom-up&quot; (unit test first, request specs or similar later) was better, but then they saw the merits of testing &quot;top-down&quot; (request specs or similar first, then unit tests for the fiddly bits) as well.</p>
<p>I think liberal applications of both of these methodologies is <em>one</em> &quot;right&quot; 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&#39;m preferring top-down though, as that&#39;s, in my opinion, testing what the client is going to be seeing, and that&#39;s what matters most. If there&#39;s a bit of
gnarly code in there, like the order inventory tracking, then that&#39;s when I&#39;d dive down into unit testing.</p>
<p>What are your thoughts on this?</p>
]]></content>
</entry>



</feed> </feed>
2 changes: 2 additions & 0 deletions _site/blogography.html
Expand Up @@ -29,6 +29,8 @@ <h2>The Blogography of a Radar</h2>
<p>The formatting for earlier posts may be a little skewiff. If you find something like that, <a href='http://github.com/radar/ryanbigg.com'>patches are very welcome</a>.</p> <p>The formatting for earlier posts may be a little skewiff. If you find something like that, <a href='http://github.com/radar/ryanbigg.com'>patches are very welcome</a>.</p>
<ul> <ul>


<li><a href="/2013/07/waiting-for-ajax-in-capybara">Waiting for AJAX in Capybara</a><abbr>09 Jul 2013</abbr></li>

<li><a href="/2013/06/finding-sql-queries-in-rails">Finding SQL queries in Rails</a><abbr>26 Jun 2013</abbr></li> <li><a href="/2013/06/finding-sql-queries-in-rails">Finding SQL queries in Rails</a><abbr>26 Jun 2013</abbr></li>


<li><a href="/2013/02/about-spec-support">About spec/support</a><abbr>02 Feb 2013</abbr></li> <li><a href="/2013/02/about-spec-support">About spec/support</a><abbr>02 Feb 2013</abbr></li>
Expand Down

0 comments on commit 8ffd337

Please sign in to comment.