Capybara and testing APIs
Before you read this blog post, go read the Capybara README. Back? Good. Did you see any mention of methods like
response? No? That’s because those don’t exist in Capybara. Let’s be very clear about this, Capybara does not have those methods. So when you’re writing a test like this:
visit '/' response.body.should have_content("Hello")
get '/' page.should have_content("Hello")
Now you know why this couldn’t possibly ever work. Because while
have_content are indeed part of the Capybara API,
response are not. If these methods are defined inside your test/spec context, some other framework is defining them, most likely RackTest, and that framework and Capybara do not interact. Capybara knows nothing about what happens when you call
get and likewise, RackTest knows nothing about what happens when you call
For most people who have used Capybara for a while, this is old news. They tried variations like the above and they didn’t work, they moved on and learned from those mistakes. The interesting question is why these methods do not exist in Capybara. Omitting these methods was a very conscious decision, a decision based on the following premise: Capybara is not a library suited to testing APIs.
There you have it. Do not test APIs with Capybara. It wasn’t designed for it.
Capybara simulates user behaviour. Its API was designed to run against as large a variety of backends as possible. To achieve this, we have placed severe restrictions on what you can and cannot do with Capybara. We have limited the API to the most common subset of actions that a user would want to perform in a web application. This immediately precludes exposing the details of HTTP. A user knows exactly how to submit a form, but they have no idea that that form is sent as an HTTP POST request with certain HTTP headers.
I’ve seen so many people hack into the internals of the RackTest driver in Capybara. I want to make it clear that doing something like the following is using a private API, which is liable to change at any point (yes, even point releases):
page.driver.browser.post('/foo', data: "here")
Don’t ever do this! Why would you? You could just use RackTest directly. In fact, as we saw before, it’s probably already included in your spec context. So why make it harder than it needs to be:
post '/foo', data: "here" last_response.body.should contain("Hello World")
Why would you write
page.driver.browser.post when you could just write
post? See that doesn’t make any sense.
By extension, Capybara assumes the response to be HTML, if you’re parsing
page.source as JSON or XML, why not just use RackTest instead? You’ll have a much nicer, cleaner API which won’t break in the future.
Use RackTest for testing APIs, use Capybara for testing user behaviour.