When checking test coverage on a Rails application, there was one code path that was missing, even though a test existed for it.
Here’s a stripped-down version of the controller spec:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
describe BooksController do
it "should get a book via an AJAX request" do
xhr :get, :show, :id => 9
response.should be_success
response.should have_text(/\bBook 9\b/)
end
it "should get a book via a non-AJAX request" do
get :show, :id => 9
response.should be_success
response.should have_text(/\bBook 9\b/)
end
end |
And here’s the relevant parts of the controller:
1
2
3
4
5
6
7
8
9
10
11
12
|
class BooksController < ApplicationController
def show
respond_to do |format|
format.js do
# Do stuff for AJAX requests
end
format.html do
# Do stuff for non-AJAX requests
end
end
end
end |
But in both cases, only the first response block was being run. Of course, if the expected response text had taken into account the differences between the two responses, and not just one piece of text, the non-AJAX spec would have failed and the problem would have been found sooner. But it wasn’t.
The problem was with the HTTP Accept header or, in this case, the lack of one. The get call did not generate an Accept header so the first response block handled the request. The xhr call, on the other hand, generated an Accept header containing text/javascript, text/html, application/xml, text/xml, */* so the correct response block would handle the request regardless of the order.
The problem never showed up in browsers because an HTTP Accept header is always being passed; Firefox, for example, sends a HTTP Accept header with a value of text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8. Only command-line tools like curl and wget don’t include text/html as part of the Accept header unless told to do so (for example, by using the --header option). I don’t know what search engines and other robots specify for an Accept header, but it might be safe to assume that they act more like curl and wget than Firefox.
So the easy fix in this case was to reverse the order of the response blocks, and remember to make sure that response blocks are always ordered so that the default response is first.