如何在 Capybara 中 POST 到 URL?

发布于 2024-09-30 13:07:42 字数 599 浏览 2 评论 0原文

刚刚从 Cucumber+Webrat 切换到 Cucumber+Capybara,我想知道如何将内容 POST 到 Capybara 中的 URL。

在 Cucumber+Webrat 中,我能够执行以下步骤:

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

但是,Capybara 文档提到:

访问方法只需要一个 参数,请求方法为 始终获取。始终获取。

如何修改我的步骤,以便 Cucumber+Capybara 对 URL 执行 POST?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

偏爱自由 2024-10-07 13:08:02

正如其他人所说,没有直接的方法可以用 Capybara 进行 POST,因为它完全与浏览器交互有关。对于 API 测试,我非常强烈推荐 rspec_api_documentation gem。

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.

在风中等你 2024-10-07 13:07:59

对于使用 RSpec 3+ 的应用程序,您不会希望使用 Capybara 发出 HTTP POST 请求。 Capybara 用于模拟用户行为,并接受由此产生的 JS 行为和页面内容。最终用户不会对应用程序中的资源形成 HTTP POST 请求,用户会单击按钮、单击 ajax 链接、拖放元素、提交 Web 表单等。

请查看 这篇关于 Capybara 和其他 HTTP 方法的博客文章。作者提出以下主张:

您是否看到任何提及 get、post 或 response 等方法的内容?不?那是因为水豚中不存在这些。让我们非常清楚这一点......Capybara 不是一个适合测试 API 的库。就这样,你就有了。不要使用 Capybara 测试 API。它不是为此而设计的。

因此,无论是否开发 API,如果您必须发出显式 HTTP POST 请求,并且它不涉及 HTML 元素和某种事件(单击、拖动、选择、聚焦等),那么就不应该这样做用水豚进行测试。如果您可以通过单击某个按钮来测试相同的功能,那么请使用 Capybara。

您可能想要的是 RSpec 请求规范 。您可以在此处进行 post 调用以及任何其他 HTTP 方法,并断言对响应的期望。您还可以模拟 n 个存根对象和方法,以断言对请求和响应之间发生的副作用和其他行为的期望。

# 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

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
你穿错了嫁妆 2024-10-07 13:07:54

虽然这不是问题的确切答案,但对我来说最好的解决方案是使用 Capybara 来模拟用户交互的规范(使用 visit),并使用 Rack Test 来测试 API(如请求)。它们可以在同一测试套件中一起使用。

将以下内容添加到规范帮助程序中可以访问 getpost 和其他机架测试方法:

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

您可能需要将机架测试规范放入 spec/requests 中 文件夹。

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.

很糊涂小朋友 2024-10-07 13:07:48

我知道答案已被接受,但我想提供更新的答案。这是 Anthony EdenCorey Haines 将 Rack::Test 传递给 Cucumber 的 World 对象:

使用 Cucumber 和 Rack::Test 测试 REST API

通过这种技术,我能够在步骤定义内直接发送 post 请求。在编写步骤定义时,从 Rack::Test API 自己的 规格

# 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

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
無心 2024-10-07 13:07:47

更新答案 2022-10-05

如果您的驱动程序没有 post (例如,Poltergeist 没有),您可以执行以下操作:

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

我们现在可以在 response.body< 上断言/代码>。

您还可以直接使用 integration_session.post(…),但我认为如果不将 POST 会话与测试的普通会话分开,可能会导致一些混乱。

正如其他地方所述,在水豚测试中,您通常希望像用户一样通过提交表单来执行 POST。我使用上面的内容来测试如果 POST 在另一个会话中发生(通过 WebSockets),用户会发生什么,所以表单不会削减它。

文档:


2014-06-22 的旧答案

如果您的驱动程序没有 post (例如,Poltergeist 没有),您可以这样做:

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

但请注意,此请求发生在新会话中,因此您必须通过 response 对象对其进行断言。

文档:

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:

请别遗忘我 2024-10-07 13:07:47

Capybara 的 visit 仅执行 GET 请求。这是设计使然。

用户要执行POST,他必须单击按钮或提交表单。没有其他方法可以使用浏览器来执行此操作。

测试此行为的正确方法是:

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

如果您想测试 API,我建议使用 spec/request 而不是 cucumber,但这只是我的想法。

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.

倾听心声的旋律 2024-10-07 13:07:46

最近,我发现了这篇很棒的博客文章。这对于像托尼这样的情况非常有用,并且您确实在您的杯子中发布一些内容:

对于我的情况,这变成了:

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

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
晒暮凉 2024-10-07 13:07:46

您可以这样做:

rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.submit :post, your_path, nil
  • 您可以将 :post 替换为您关心的任何方法,例如 :put:delete
  • your_path 替换为您想要的 Rails 路径,例如 rack_test_session_wrapper.submit :delete, document_path(Document.last), nil 会删除我的应用程序中的最后一个文档。

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文