How do you POST to a URL in Capybara?
Asked Answered
S

8

37

Just switched from Cucumber+Webrat to Cucumber+Capybara and I am wondering how you can POST content to a URL in Capybara.

In Cucumber+Webrat I was able to have a step:

When /^I send "([^\"]*)" to "([^\"]*)"$/ do |file, project|
  proj = Project.find(:first, :conditions => "name='#{project}'")
  f = File.new(File.join(::Rails.root.to_s, file))
  visit "project/" + proj.id.to_s + "/upload",
        :post, {:upload_path => File.join(::Rails.root.to_s, file)}
end

However, the Capybara documentation mentions:

The visit method only takes a single parameter, the request method is always GET.always GET.

How do I modify my step so that Cucumber+Capybara does a POST to the URL?

Schultz answered 3/11, 2010 at 4:23 Comment(8)
Why don't fill your for with your file and submit it. In cucumber scenario, you need to be like a user and do only clik by click. A user can't made a post method without some form.Krupp
@Krupp Thanks for the suggestion, I was abusing cucumber a little as there is no form for this particular action. It is for different clients (software) to dump content to the application. I want to simulate clients dumping data into the application.Schultz
the capybara using is not good if you don't use some javascript.Krupp
@Krupp - That is an interesting idea, attempt to post the data via javascript?Schultz
no migrating from webrat to capybara it's only if you really need test some javascript in your code. If you haven't, it's not a good choice to migrate.Krupp
You can try to add a form somewhere in your app to do this post request. And use it to made your post.Krupp
works for me: https://mcmap.net/q/426208/-rspec-capybara-how-to-simulate-incoming-post-requests-rack-test-won-39-t-workGerdes
To the naysayers: while it should not be possible for a user to post without a form, it is; I found a situation where hitting "back" causes Chrome to repeat the POST. Naturally I want to test that. (And of course, the user could use curl.) Sometimes we need to drop down an abstraction level.Teddman
S
24

More recently I found this great blog post. Which is great for the cases like Tony and where you really want to post something in your cuke:

For my case this became:

def send_log(file, project)
  proj = Project.find(:first, :conditions => "name='#{project}'")
  f = File.new(File.join(::Rails.root.to_s, file))
  page.driver.post("projects/" + proj.id.to_s + "/log?upload_path=" + f.to_path)
  page.driver.status_code.should eql 200
end
Schultz answered 11/4, 2011 at 1:53 Comment(3)
Unfortunately I'm getting undefined method 'post' for #<Capybara::Selenium::Driver:0x3f75e10> (NoMethodError).Brei
Looks like the latest versions of capybara break this -- my gemfile currently restricts to an earlier version: gem 'capybara', "0.4.1.2"Schultz
Yep, this happened for me too. Used to work on another computer a few months ago. I guess running an earlier version will fix it.Cordeelia
G
21

You could do this:

rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.submit :post, your_path, nil
  • You can replace :post which whatever method you care about e.g. :put or :delete.
  • Replace your_path with the Rails path you want e.g. rack_test_session_wrapper.submit :delete, document_path(Document.last), nil would delete the last Document in my app.
Grandpapa answered 8/3, 2012 at 11:48 Comment(4)
I get undefined method submit' for #<Capybara::Poltergeist::Driver:0x007f8cdcc699c0>` - Capybara 2.9.0Karlene
Dont use Capybara for making HTTP POST requests. Its designed to emulate a user using a web browser. The user doesnt form HTTP POST requests, the user clicks buttons and links and submits forms and such. Use RSpec Request specs for testing responses from POST, PUT, DELETE requests. Check out this blog post: matthewlehner.net/rails-api-testing-guidelinesDandiprat
In my case, the user actually does submit an HTTP request from their command line, which returns some data, including a unique URL, which they then visit in their browser. The integration test I need would perform a GET request, receive data including a URL, and then follows the URL in the browser and ensures the displayed data in the browser matches the data returned by the GET request. The problem with blanket statements like, "The user doesn't..." is that they assume you know what everyone's users do.Fendley
@Fendley Todd merely said that Capybara was designed to emulate a user using a browser. Your users do not use a browser, do not expect Capybara to be able mimic them.Cheremkhovo
P
14

Updated answer 2022-10-05

If your driver doesn't have post (Poltergeist doesn't, for example), you can do this:

response = nil
open_session do |session|
  session.post("/mypath", params: { foo: "bar" })
  response = session.response
end

We can now e.g. assert on response.body.

You can also use integration_session.post(…) directly, but I think that can cause some confusion by not separating the POST session from the test's ordinary session.

As has been stated elsewhere, in a Capybara test you typically want to do POSTs by submitting a form just like the user would. I used the above to test what happens to the user if a POST happens in another session (via WebSockets), so a form wouldn't cut it.

Docs:


Old answer from 2014-06-22

If your driver doesn't have post (Poltergeist doesn't, for example), you can do this:

session = ActionDispatch::Integration::Session.new(Rails.application)
response = session.post("/mypath", my_params: "go_here")

But note that this request happens in a new session, so you will have to go through the response object to assert on it.

Docs:

Primate answered 22/6, 2014 at 16:50 Comment(3)
This seems to be the most up-to-date response and works with Webkit driver as well (which doesn't have the post method)Liu
This is beautiful - needed to post though the api, to see something happen in the user's browser via websockets. THANKS!Tubman
The response object returned by post is just the status code, you can access the response object with session.response afterwards, which also has the #body method. Together with this piece of information, POSTing outside Capybara works perfectly!Southsoutheast
L
9

Capybara's visit only does GET requests. This is by design.

For a user to perform a POST, he must click a button or submit a form. There is no other way of doing this with a browser.

The correct way to test this behaviour would be:

visit "project/:id/edit" # This will only GET
attach_file "photo", File.open('cute_photo.jpg')
click_button 'Upload' # This will POST

If you want to test an API, I recommend using spec/request instead of cucumber, but that's just me.

Leong answered 3/11, 2010 at 10:24 Comment(3)
This sounds right. I have a situation where I want to make sure users with a specific permission cannot POST to a URL. So I guess what you're saying is Capybara+Cucumber should not be used for that.Skeens
It may be useful to test a POST. For example, if can use it to test your code is resilient against bots trying to post things where they should not be allowed to.Dustheap
Its needed when testing 3rd party callbacks that make post requests.Timepiece
S
5

I know the answer has already been accepted, but I'd like to provide an updated answer. Here is a technique from Anthony Eden and Corey Haines which passes Rack::Test to Cucumber's World object:

Testing REST APIs with Cucumber and Rack::Test

With this technique, I was able to directly send post requests within step definitions. While writing the step definitions, it was extremely helpful to learn the Rack::Test API from it's own specs.

# feature
  Scenario: create resource from one time request
    Given I am an admin
    When I make an authenticated request for a new resource
    Then I am redirected  
    And I see the message "Resource successfully created" 

# step definitions using Rack::Test
When /^I make an authenticated request for a new resource$/ do
  post resources_path, :auth_token => @admin.authentication_token
  follow_redirect!
end

Then /^I am redirected$/ do
  last_response.should_not be_redirect
  last_request.env["HTTP_REFERER"].should include(resources_path)
end

Then /^I see the message "([^"]*)"$/ do |msg|
  last_response.body.should include(msg)
end
Sophia answered 31/10, 2011 at 20:5 Comment(1)
Although it's not required for your example, it's better to use page.driver.post so it's in the same context as other steps.Faitour
C
3

Although, not an exact answer to the question, the best solution for me has been to use Capybara for specs that simulate user interaction (using visit), and Rack Test for test API like requests. They can be used together within the same test suite.

Adding the following to the spec helper gives access to get, post and other Rack test methods:

RSpec.configure do |config|
  config.include Rack::Test::Methods

You may need to put the Rack Test specs in a spec/requests folder.

Cordeelia answered 27/5, 2015 at 21:31 Comment(2)
This seems like an unconventional use of Capybara, and while it may allow the OP to achieve desired functionality, it may induce an antipattern into the test suite. RSpec Request specs are much better suited to testing the behavior of an HTTP POST to an application.Dandiprat
It's not that simple, anymore, as Rack::Test::Methods requires the instance method app to return an instance of the Rack application. github.com/rack-test/rack-test/blob/master/lib/rack/test/…Bedmate
D
1

With an application using RSpec 3+, you would not want to make an HTTP POST request with Capybara. Capybara is for emulating user behavior, and accepting the JS behavior and page content that results. An end user doesnt form HTTP POST requests for resources in your application, a user clicks buttons, clicks ajax links, drags n drops elements, submits web forms, etc.

Check out this blog post on Capybara and other HTTP methods. The author makes the following claim:

Did you see any mention of methods like get, post or response? No? That’s because those don’t exist in Capybara. Let’s be very clear about this...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.

So, developing an API or not, if you have to make an explicit HTTP POST request, and it does not involve an HTML element and some sort of event (click, drag, select, focusout, whatever), then it shouldn't be tested with Capybara. If you can test the same feature by clicking some button, then do use Capybara.

What you likely want is RSpec Request specs. Here you can make post calls, and any other HTTP method as well, and assert expectations on the response. You can also mock n stub objects and methods to assert expectations in regards to side effects and other behaviors that happen in between your request and the response.

# spec located in spec/requests/project_file_upload_spec.rb
require "rails_helper"

RSpec.describe "Project File Upload", type: :request do

  let(:project) { create(:project) }
  let(:file)    { File.new(File.join(::Rails.root.to_s, 'path/to/file.ext')) } # can probably extract this to a helper...

  it "accepts a file uploaded to a Project resource" do

    post "project/#{project.id}/upload", upload_path: file

    expect(response).to be_success
    expect(project.file?).to eq(true)
    # expect(project.file).not_to eq(nil)
    expect(response).to render_template(:show)
  end

end
Dandiprat answered 8/4, 2017 at 21:51 Comment(0)
B
0

As others have said, there’s no direct way of doing a POST with Capybara because it’s all about browser interaction. For API testing, I’d very highly recommend the rspec_api_documentation gem.

Bair answered 22/4, 2020 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.