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?
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
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.
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:
https://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html#method-i-open_session
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:
http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html
http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html
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.
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
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.
Related
I am new to ruby on rails. I am getting an undefined method error when I run rspec on comment_spec.rb
1) after_save calls 'Post#update_rank' after save
Failure/Error: request.env["HTTP_REFERER"] = '/'
NameError:
undefined local variable or method `request' for #<RSpec::ExampleGroups::AfterSave:0x007fa866ead8d0>
# ./spec/models/vote_spec.rb:45:in `block (2 levels) in <top (required)>'
This is my spec:
require 'rails_helper'
describe Vote do
....
describe 'after_save' do
it "calls 'Post#update_rank' after save" do
request.env["HTTP_REFERER"] = '/'
#user = create(:user)
#post = create(:post, user: #user)
sign_in #user
vote = Vote.new(value:1, post: post)
expect(post). to receive(:update_rank)
vote.save
end
end
Any help that you would have would be greatly appreciated...
I was following the apirails book tutorial chapter 3 here
http://apionrails.icalialabs.com/book/chapter_three
I was receiving the same error and DrPositron's solution worked for me, all green again. Just needed to add ":type => :controller" on my block like so:
describe Api::V1::UsersController, :type => :controller do
end
Hope this helps someone
OK here's the deal.
Vote is a model, i suppose.
You are writing a test for that model.
There's a difference between model tests ("the domain logic is doing what its supposed to") and feature/integration tests ("the application is behaving the way its supposed to").
The request variable is associated with feature or controller tests.
So what's wrong?
You are not logging in users in model tests, just check if the update_rank method is being called on save, thats it.
No user-interaction jazz in model tests.
Hope that helps!
Cheers
Jan
So Louis, just to expand on Jan's response:
You appear to be writing a model spec. The purpose of a model spec is simply to test how your model classes work, and that behavior is testable without having to pay any attention to the application logic around signing in, making "requests" to particular controllers, or visiting particular pages.
You're essentially just testing a couple related Ruby classes. For this, we don't need to think about the whole app -- just the classes we're testing.
As a consequence, RSpec doesn't make certain methods available in the spec/models directory -- you're not supposed to think about requests or authentication in these tests.
It looks like your test is simply designed to make sure that when you create a vote for a post, it updates that post's rank (or, specifically, call's that post's update_rank method). To do that, you don't need to create a user, or sign a user in, or pay any attention to the request (what request would we be referring to? We're just testing this as if in Rails console, with no HTTP request involved).
So you could basically remove the first four lines of your test -- apart from the line creating your post, and the post's user if it's necessary (if the post model validates the presence of a user). Don't sign a user in -- we're just testing a Ruby class. There's no concept of a website to sign into in this test.
Then, as a last thing to take care of to get your spec to pass, make sure to refer to the post you create by the right name. Right now, you're creating a post and assigning it to the #post variable, but then you're referring to just post later on. post doesn't exist; just #post. You'll have to pick one variable name and stick with it.
Also, if you are using RSpec 3, file type inference is now disabled by default and must be opted in as described here. If you're new to RSpec, a quick overview of the canonical directory structure is here.
For example, for a controller spec for RelationshipsController, insert , :type => :controller as such:
describe RelationshipsController, :type => :controller do
#spec
end
I'm trying to build a request spec which tests the full gamut of creating a new user via devise API.
I currently have this sitting in RegistrationsController spec, but this won't work if I want to follow the mail link to the confirmations controller.
I haven't been able to find a good example of how people have tested the 'hand-off' from one controller to another and the intermittent 'steps' (we have custom devise methods scattered throughout the process which this test will encompass too).
it "creates a user, sends a welcome email, confirms the user, and registers the user in email campaigns" do
post :create, {user: new_user_params}
last_email = ActionMailer::Base.deliveries.last.body
ConfirmationsController.any_instance.should_receive(:after_filter_method_to_subscribe_user)
redirect_to confirmation_link(last_email) # helper method
last_email.should include("Thanks for joining!")
user = User.find_by_first_name(new_first_name)
user.confirmed?.should be_true
user.email_lists.should_not be_empty
end
Edit:
I should also add that I need http_basic_auth to run the spec which I'm including in a spec/support file and sets the request.env['HTTP_AUTHORIZATION'] to variables stored in the API::Base controller. I currently have nil as a request obect when running specs in the spec/request folder, which I'll need to run the specs.
Edit:
Thanks to people who've taken a look. I figured it out after piecing together two SO searches and the code I had. I'll post an answer for future SO'ers when I can.
I figured this out shortly after posting my question with good luck finds on more google searches. Kudos to a couple SO references ~> request spec relish: http://goo.gl/iBg7v1 && setting request headers for http basic auth in request specs: http://goo.gl/hdDBMd
My spec turned out to look something like the below Hope this helps someone not waste 4 hours like me :).
spec/requests/api/user_registration_spec.rb.
it "sends a welcome email, confirms the user, and signs the user up to email campaigns" do
email_list = FactoryGirl.create(:email_list, name: "funky-email-campaign")
user_name = Api::RegistrationsController::USER
password = Api::RegistrationsController::PASSWORD
# post to /users/registration
post api_registrations_path({user: new_user_params}), nil , {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user_name, password)}
last_email = ActionMailer::Base.deliveries.last.body
UserService.should_receive(:subscribe_to_email).and_call_original # check that after_filter is called
get confirmation_link(last_email) # follow link in email (/users/confirmation)
response.should redirect_to(custom_path) # tests after_confirmation_path_for override
last_email.should include(new_first_name)
last_email.should include("Thanks for joining!")
user = User.find_by_first_name(new_first_name)
user.confirmed?.should be_true
user.email_lists.first.name.should eq(email_list.name)
end
I need to receive incoming emails as multipart-formdata via a POST request from Cloudmailin. The POST resembles the following:
Parameters: {"to"=>"<email#exmaple.comt>", "from"=>"whomever#example", "subject"=>"my awesome subject line....
Actually, receiving and parsing emails is super easy because the email is just posted as params: params[:to], params[:from], etc. However, how do I simulate this POST request in rails?
I built a dummy rails app to test out Cloudmailin, so I have an actual request. However, it's a 6k character file, so I'd like to load this file as the parameters of the POST request. I've tried using the built rails post and post_via_redirect methods to load a file, but it escapes all of the parameters( \"to\"), which is no good. Any ideas?
So, I ended up doing:
#parameters = { "x_to_header"=>"<#{ #detail.info }>",
"to"=>"<#{ #account.slug }#cloudmailin.net>",
"from"=>"#{ #member.email }",
"subject"=>"meeting on Monday",
"plain"=>"here is my message\nand this is a new line\n\n\nand two new lines\n\n\n\nand a third new line"
}
then just:
post "/where_ever", #parameters
seems to get the job done for now
A simple way would probably to execute a script in capybara. Just make sure with the #javascript tag, then load any page in your app that has jQuery installed (technically, you don't need this, but it's much easier. Then:
When /^I get a post request from Cloudmailin$/ do
visit '/some/page/with/jquery'
page.execute_script(%{$.post("/some/path?to=some_email&etc=etc");})
end
There's the simple post capybara method too, but I'm not too sure about how that works. Might be worth looking into.
I saw this answer last night when I was updating some of my own test code for Rails 3.2.8, and which uses the Mail gem, and thought I'd share what I found. The test code is for an application that needs to take a POST from Cloudmailin and then process it to create a new user with Devise, and then send a confirmation to that user, which the user can then follow to choose a password. Here is my controller spec:
require 'spec_helper'
describe ThankyouByEmailController do
message1 = Mail.new do
from "Frommy McFromerton <frommy.mcfrommerton#gmail.com>"
to "toey.receivesalot#gmail.com"
subject "cloudmailin test"
body 'something'
text_part do
body 'Here is the attachment you wanted'
end
html_part do
content_type 'text/html; charset=UTF-8'
body '<h1>Funky Title</h1><p>Here is the attachment you wanted</p>'
end
end
describe "creating new users" do
describe "unregistered FROM sender and Unregistered TO receiver" do
it "should create 2 new users" do
lambda do
post :create, :message => "#{#message1}"
end.should change(User, :count).by(2)
end
end
end
end
Hope this clean up your own tests. And for anyone else interested in testing the mail gem, mikel's documentation has come a long way for same:
https://github.com/mikel/mail
I am trying to test my controllers, but some of the actions in my controller expect the user to be logged in. How will I test them? Do I mess with the session variable directly? Also, what if a lot of the actions expect the user to be logged in? Should I set up a before action, and log the user in there?
Another idea I had was that I could test them in an integration test, and do a post on the login form, before I actually test the desired action. Something like:
def setup
# log the user in, this will happen before every test
end
# integration test
test "I should see my posts" do
#setup should have happened before this, and I should be logged in
get posts_path
assert ...
end
Is this the way to test these actions? Am I missing something?
Depending on your authentication framework you use there are several ways. Devise for example has some TestHelpers, that make it easy to login users without having go through the actual webpage in functional tests. If thats not an option, like soundsop said, browser testing. (look at, from high to low: cucumber, capybara, selenium/...)
The Book includes some testing examples in their depot application:
test "should login" do
dave = users(:one)
post :create, :name => dave.name, :password => 'secret'
assert_redirected_to admin_url
assert_equal dave.id, session[:user_id]
end
Full details in the "Authenticating Users" section.
You can either make fake user credentials in the setup, or you can stub out the method which checks the credentials using a mocking library. I've done both and don't have a firm preference.
Anyone have any tips for best practices for mocking out facebook requests in functional tests? Is it just as simple as adding all of the proper params to the request? Is there a way to stub those out?
I'm using facebooker, which comes with a mock service:
# A mock service that reads the Facebook response from fixtures
# Adapted from http://gist.github.com/44344
#
# Facebooker::MockService.fixture_path = 'path/to/dir'
# Facebooker::Session.current = Facebooker::MockSession.create
But when I write a basic get test, it tries to redirect the browser to the facebook page for adding the app, which I assume indicates that the mocking isn't working.
test "loads respondent" do
Facebooker::Session.current = Facebooker::MockSession.create
get :index
puts #response.body # => <html><body>You are being redirected.</body></html>
end
I got this working with the latest version of facebooker (1.0.58):
# test_helper.rb
require 'facebooker/mock/session'
require 'facebooker/mock/service'
Facebooker::MockService.fixture_path = File.join(RAILS_ROOT, 'test', 'fixtures', 'facebook')
Obviously you will have to create the facebook directory in fixtures, or put it wherever. Inside you have to add a folder for each facebook method, and an xml file for the different types of responses you want to test for. I had to add facebook.users.getInfo and facebook.users.hasAppPermission. The easiest is just to add a file named default.xml with the example code from the facebook wiki for those actions.
# Controller test
test "facebook action" do
get :index, {:fb_sig_added => true}, :facebook_session => Facebooker::MockSession.create
assert_response :success
end
The fb_sig_added param is necessary as far as I can tell, because the internal facebooker logic checks the params directly before checking the session on that one. Which seems a bit wanky to me but maybe there's a reason for that.