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 have just started a new rails app. So far there are no controllers or models there is simply the two engines: refinery and spree working next to each other.
They are mounted as following:
Store::Application.routes.draw do
mount Spree::Core::Engine, :at => '/shop'
mount Refinery::Core::Engine, :at => '/'
end
Now i have set up a few pages using refinery so when I go to / then i see the refinery home page and can click the about us page etc. When i go to /shop then i see the spree section of the site which is also working well.
Now i would like to write a small test that the spree engine is correctly mounted at '/shop'. I tried with the following test:
require 'test_helper'
class SpreeEngineTest < ActionDispatch::IntegrationTest
test "has been correctly mounted" do
get "/shop"
assert_response :success
end
end
But it fails with the result:
Expected response to be a <:success>, but was <302>
I looked into the body of the request and it contains the following:
"<html><body>You are being redirected.</body></html>"
I am using the standard testunit package and rails 3.2
Thanks for your help!
Maybe you should use assert_redirected_to instead of assert_response.
http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html
Sorry for the late answer. The reason is that Refinery will redirect you to the create user dialog when it detects that no users with the correct roles exists in the database. So my guess is that you're running this test without any users registered in the test database, and that causes the redirect.
In your integration test, add the following setup method:
def setup
user = Refinery::User.new(:username => 'testuser', :email => 'test#example.com', :password => 'test', :password_confirmation => 'test')
assert user.create_first
end
This will ensure that a default user with the right roles exist and the rest of the test should be ok.
I've been struggling with creating a login function that should be executed before any rspec test is run.
What I have right now is:
def login
post "/session", { "session[username]" => "bjones" }
end
in my spec_helper.rb file
Then, I have the following in one of my spec.rb files in the requests directory.
require 'spec_helper'
describe "Sessions" do
describe "GET /dashboards" do
login
it "displays the dashboard" do
get dashboard_path
puts response.body
end
end
end
However, when I try running the test, I get:
undefined method `post' for #<Class:0x4f08828> (NoMethodError)
I'm pretty new to rails and completely new to testing and rspec so maybe there's something fundamental I'm missing here. Basically, all I want to do is set that session variable so that when the test is run I will be logged in. Perhaps a different approach would be better? Or maybe I need to put that login function in a different place?
I came across this answer which was sort of useful but it's not for rspec so I'm having trouble understanding where such a helper function would go.
Try
let(:login) {
post "/session", { "username" => "bjones" }.to_json
}
This might have to be revised to use .to_json or not, depending on what content type the controller accepts.
I'm working on a MVP (minimum viable product). In order to offer a simpler way to secure the admin pages, I just did add http_basic_authenticate_with to my AdminController.
Problem is that when I want to test my AdminController, I get "unauthorized" (401) for not being logged in.
In this scenario, it's irrelevant to test the authentication - it's just temporary, and as soon I go to the next sprint, it's going to removed -, so I'm trying to skip it within RSpec.
Problem is I tried many ways, and none seems to be working.
For example, I tried to modify the http_basic_authenticate_with in order to avoid the authentication. Like this:
require 'spec_helper'
module ActionController
module HttpAuthentication
module Basic
def http_basic_authenticate_with(*args)
end
end
end
end
describe Admin::SubscribersController do
describe "GET 'index'" do
it "should be OK" do
get 'index'
response.should be_successful
end
end
end
But when I run it, it still returns "false" for that simple test.
Btw, in order to simplify this test, I just have an empty index action on my AdminController and an empty view (index.html.erb).
Finally I got it working.
Something as stated in the docs didn't work for me:
get 'index', nil, 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials("admin", "password")
So I tried an "old" approach to do it, which is to set request.env['HTTP_AUTHORIZATION'] :
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("admin","password")
None of the other solutions worked, so I will just keep in the meanwhile with this one.
Thanks.
If it is ok to skip authentication for all tests for a controller, here's the technique I'm using on a current project.
unless Rails.env.test?
http_basic_authenticate_with name: "slothbear", password: "kuniklo"
end
In Rails 5.x, this works:
allow(subject).to receive(:authenticate_or_request_with_http_basic)
.with(anything).and_return true
In Rails 6.x, this works:
allow(subject).to receive(:http_basic_authenticate_or_request_with)
.with(anything).and_return true
This is because http_basic_authenticate_with is a class-level method that adds a before_action that actually calls one of these two methods under the hood.
You can see which one to use by checking out http_authentication.rb here for Rails 6 or here for Rails 5
You can or even should test authentication. Write test for unauthenticated (it is now) and authenticated.
See Testing HTTP Basic Auth in Rails 2.2+ it should help.
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.