ruby on rails - Usando Rspec, ¿cómo pruebo el formato JSON de mi controlador en Rails 3.0.11?
ruby-on-rails api (2)
Recorrí la web, pero, por desgracia, parece que no puedo hacer que Rspec envíe correctamente el tipo de contenido para que pueda probar mi API JSON. Estoy usando la gema RABL para plantillas, Rails 3.0.11 y Ruby 1.9.2-p180.
Mi salida de curl, que funciona bien (debería ser un 401, lo sé):
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."}
Muestra de uno de mis casos de prueba:
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
Estas pruebas nunca pasan, siempre me redireccionan y mi tipo de contenido siempre es text/html
, independientemente de cómo parezca especificar el tipo en mi bloque anterior:
# 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
Aquí está el resultado de 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)>''
Como pueden ver, obtengo la redirección 302 para el formato HTML, aunque intento especificar ''application / json''.
Aquí está mi application_controller.rb
, con el 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
Y también applications_controller.rb
, específicamente la acción ''crear'' que es lo que estoy tratando de probar. Es bastante feo en este momento porque estoy usando state_machine
y anulando el método de eliminación.
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
He visto el código fuente aquí: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/responder.rb , y parece que debería responder correctamente, así como número de preguntas sobre desbordamiento de pila que parecen tener problemas similares y posibles soluciones, pero ninguna funciona para mí.
¿Qué estoy haciendo mal?
Intente mover la clave de :format
dentro del hash de parámetros de la solicitud, así:
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
¡Déjame saber como va! esa es la forma en que he establecido mis pruebas, ¡y están funcionando bien!
Me doy cuenta de que la configuración :format => :json
es una solución (como se señaló anteriormente). Sin embargo, quería probar las mismas condiciones que usarían los clientes de mi API. Mis clientes no establecerían el parámetro de :format
en su lugar, establecerían el encabezado Accept
HTTP. Si está interesado en esta solución, aquí está lo que utilicé:
# 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