J'ai parcouru le Web, mais, hélas, je n'arrive pas à faire en sorte que Rspec envoie correctement le type de contenu afin que je puisse tester mon API JSON. J'utilise la gemme RABL pour les modèles, Rails 3.0.11 et Ruby 1.9.2-p180.
Ma sortie curl, qui fonctionne bien (devrait être un 401, je sais):
mrsnuggles:tmp gaahrdner$ curl -i -H "Accept: application/json" -X POST -d @bleh http://localhost:3000/applications
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
Cache-Control: no-cache
X-Ua-Compatible: IE=Edge
X-Runtime: 0.561638
Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18)
Date: Tue, 06 Mar 2012 01:10:51 GMT
Content-Length: 74
Connection: Keep-Alive
Set-Cookie: _session_id=8e8b73b5a6e5c95447aab13dafd59993; path=/; HttpOnly
{"status":"error","message":"You are not authorized to access this page."}
Échantillon d'un de mes cas de test:
describe ApplicationsController do
render_views
disconnect_sunspot
let(:application) { Factory.create(:application) }
subject { application }
context "JSON" do
describe "creating a new application" do
context "when not authorized" do
before do
json = { :application => { :name => "foo", :description => "bar" } }
request.env['CONTENT_TYPE'] = 'application/json'
request.env['RAW_POST_DATA'] = json
post :create
end
it "should not allow creation of an application" do
Application.count.should == 0
end
it "should respond with a 403" do
response.status.should eq(403)
end
it "should have a status and message key in the hash" do
JSON.parse(response.body)["status"] == "error"
JSON.parse(response.body)["message"] =~ /authorized/
end
end
context "authorized" do
end
end
end
end
Ces tests ne réussissent jamais, je suis toujours redirigé et mon type de contenu est toujours text/html
, quelle que soit la façon dont je semble spécifier le type dans mon bloc avant:
# nope
before do
post :create, {}, { :format => :json }
end
# nada
before do
post :create, :format => Mime::JSON
end
# nuh uh
before do
request.env['ACCEPT'] = 'application/json'
post :create, { :foo => :bar }
end
Voici la sortie rspec:
Failures:
1) ApplicationsController JSON creating a new application when not authorized should respond with a 403
Failure/Error: response.status.should eq(403)
expected 403
got 302
(compared using ==)
# ./spec/controllers/applications_controller_spec.rb:31:in `block (5 levels) in <top (required)>'
2) ApplicationsController JSON creating a new application when not authorized should have a status and message key in the hash
Failure/Error: JSON.parse(response.body)["status"] == "errors"
JSON::ParserError:
756: unexpected token at '<html><body>You are being <a href="http://test.Host/">redirected</a>.</body></html>'
# ./spec/controllers/applications_controller_spec.rb:35:in `block (5 levels) in <top (required)>'
Comme vous pouvez le voir, j'obtiens la redirection 302 pour le format HTML, même si j'essaie de spécifier "application/json".
Voici mon application_controller.rb
, avec le bit rescue_from:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
protect_from_forgery
helper_method :current_user
helper_method :remove_dns_record
rescue_from CanCan::AccessDenied do |exception|
flash[:alert] = exception.message
respond_to do |format|
h = { :status => "error", :message => exception.message }
format.html { redirect_to root_url }
format.json { render :json => h, :status => :forbidden }
format.xml { render :xml => h, :status => :forbidden }
end
end
private
def not_found(exception)
respond_to do |format|
h = { :status => "error", :message => exception.message }
format.html { render :file => "#{Rails_ROOT}/public/404.html", :status => :not_found }
format.json { render :json => h, :status => :not_found }
format.xml { render :xml => h, :status => :not_found }
end
end
end
Et aussi applications_controller.rb
, en particulier l'action "créer" qui est ce que j'essaie de tester. C'est assez moche pour le moment car j'utilise state_machine
et en remplaçant la méthode de suppression.
def create
# this needs to be cleaned up and use accepts_attributes_for
@application = Application.new(params[:application])
@environments = params[:application][:environment_ids]
@application.environment_ids<<@environments unless @environments.blank?
if params[:site_bindings] == "new"
@site = Site.new(:name => params[:application][:name])
@environments.each do |e|
@site.siteenvs << Siteenv.new(:environment_id => e)
end
end
if @site
@application.sites << @site
end
if @application.save
if @site
@site.siteenvs.each do |se|
appenv = @application.appenvs.select {|e| e.environment_id == se.environment_id }
se.appenv = appenv.first
se.save
end
end
flash[:success] = "New application created."
respond_with(@application, :location => @application)
else
render 'new'
end
# super stinky :(
@application.change_servers_on_appenvs(params[:servers]) unless params[:servers].blank?
@application.save
end
J'ai regardé le code source ici: https://github.com/Rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb , et il semble qu'il devrait répondre correctement, ainsi qu'un certain nombre de questions sur le débordement de pile qui semblent avoir des problèmes similaires et des solutions possibles, mais aucune ne fonctionne pour moi.
Qu'est-ce que je fais mal?
Essayez de déplacer le :format
clé à l'intérieur du hachage de paramètres de la demande, comme ceci:
describe ApplicationsController do
render_views
disconnect_sunspot
let(:application) { Factory.create(:application) }
subject { application }
context "JSON" do
describe "creating a new application" do
context "when not authorized" do
it "should not allow creation of an application" do
params = { :format => 'json', :application => { :name => "foo", :description => "bar" } }
post :create, params
Expect(Application.count).to eq(0)
expect(response.status).to eq(403)
expect(JSON.parse(response.body)["status"]).to eq("error")
expect(JSON.parse(response.body)["message"]).to match(/authorized/)
end
end
context "authorized" do
end
end
end
end
Faites-moi savoir comment ça se passe! c'est la façon dont j'ai réglé mes tests, et ils fonctionnent très bien!
Je me rends compte que le réglage :format => :json
est une solution (comme indiqué ci-dessus). Cependant, je voulais tester les mêmes conditions que les clients de mon API utiliseraient. Mes clients ne définiraient pas le :format
, ils définiraient plutôt l'en-tête HTTP Accept
. Si vous êtes intéressé par cette solution, voici ce que j'ai utilisé:
# api/v1/test_controller_spec.rb
require 'spec_helper.rb'
describe Api::V1::TestController do
render_views
context "when request sets accept => application/json" do
it "should return successful response" do
request.accept = "application/json"
get :test
response.should be_success
end
end
end