Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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.

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.

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.

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:

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

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.

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top