Devise, cucumber, and capybara not authenticating on JSON request
I am using backbone.js with rails. My controller accepts and returns JSON. I had all my tests working fine until I added in devise authentication. I cannot get cucumber to stay authenticated.
I have tried everything mentioned on:
- devise wiki
- here - standard login (go to page, fill in form, click button), setting cookie and having a special application before filter, doing the latter but in rack (couldn't get this to even run)
- doing the above cookie/before_filter using this set_cookie method
All to no avail. The login returns fine (200), but when I send my request I get a 401 response.
Here is my controller code:
class ContactsController < ApplicationController
before_filter :authenticate_user!
### backbone
def index
render :json => current_user.person.contacts
end
def show
render :json => current_user.person.contacts.find(params[:id])
end
def create
contact = current_user.person.contacts.create! params
render :json => contact
end
def update
contact = current_user.person.contacts.find(params[:id])
contact.update_attributes! params
render :json => contact
end
def destroy
contact = current_user.person.contacts.find(params[:id])
contact.destroy
render :json => contact #TODO: change to render nothing
end
Here is one cucumber feature:
Scenario: View contacts
Given I am logged in as "testy@testing.com"
And I send and accept JSON
And the following contacts:
|name|
|name 1|
|name 2|
|name 3|
|name 4|
When I send a GET request for "/contacts"
Then the response should be "200"
And the JSON response should be an array with 4 "name" elements
And I should get the following contacts:
|name|
|name 1|
|name 2|
|name 3|
|name 4|
The JSON methods are using Rack directly. I suspect this is where my problems lie, but I don't know how to get past it.
Here are the cuke JSON steps:
World(Rack::Test::Methods)
Given /^I send and accept JSON$/ do
header 'Accept', 'application/json'
header 'Content-Type', 'application/json'
end
When /^I send a GET request for "([^\"]*)"$/ do |path|
get path
end
When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, table|
params = table.hashes.flatten[0].to_json
post path, params
end
When /^I send a PUT request to "([^\"]*)" with the following:$/ do |path, table|
params = table.hashes.flatten[0].to_json
put path, params
end
When /^I send 开发者_如何学Pythona DELETE request to "([^\"]*)"$/ do |path|
delete path
end
Then /^the response should be "([^\"]*)"$/ do |status|
last_response.status.should == status.to_i
end
Then /^the JSON response should have 1 "([^\"]*)" element$/ do |name|
page = JSON.parse(last_response.body)
page[name].should be
end
Then /^the JSON response should be an array with (\d+) "([^\"]*)" elements$/ do |number_of_children, name|
page = JSON.parse(last_response.body)
page.map { |d| d[name] }.length.should == number_of_children.to_i
end
Your Cuke looks like a RSpec test really... Since you're not actually rendering anything except direct output from the controller, I usually skip the heavier Cuke tests for more nimble RSpec tests. Cuke is left for integration usually testing controller and view in a sequence of scenarios that drive a business outcome.
The key issue may be with:
Given I am logged in as "testy@testing.com"
I'm not sure what that does. If it simply displays a login form and does nothing with it, you'll get a 200. If you want to solve that bit, step through the logged in step and make sure that you have a user_session by the time you hit the post/get calls.
user_signed_in?.should be_true
current_user.should_not be_nil
user_session.should_not be_nil
If you want to just simply test the direct results of a single controller action, try using RSpec.
Here's a sample spec for a controller that I have in RSpec: (smallest one I could find w/ auth)
require "spec_helper"
describe GuestsController do
include Devise::TestHelpers
let(:user) { Factory(:user) }
context "#create" do
it "should create an anonymous user" do
User.should_receive(:anonymous!).and_return(user)
post :create
response.should redirect_to(meals_path)
end
it "should not create a user" do
User.should_not_receive(:anonymous!)
sign_in user
post :create
response.should redirect_to(meals_path)
end
end
end
You can even stub out authentication if you don't want to call sign_in
before do
request.env['warden'] = mock(Warden, authenticate: mock_user,
authenticate!: mock_user)
end
Good luck. debugger
is your friend!
精彩评论