How to assert response code in Rails controller test? - ruby-on-rails

EDIT
Edited to change to a question about the test rather than code, as I see the application behaves correctly.
I'm writing a Rails 3 app which is purely a RESTful web service (i.e. no views). I have a User model, where the username is unqiue
class User < ActiveRecord::Base
validates_uniqueness_of :username
end
In my controller, I have the following code to handle a new user being created:
def create
#user = User.new(ActiveSupport::JSON.decode(request.raw_post))
if #user.save
puts "Added user #{#user.username}"
format.json { render :json => "" }
else
puts "Failed to add user: #{#user.errors.to_json}"
render json: #user.errors, status: :unprocessable_entity
end
end
I then have a functional test which creates a user with the same username as an existing user:
test "should not create user with duplicate username" do
#jim = users(:jim)
post '/users', #jim.to_json, "CONTENT_TYPE" => "application/json"
assert_response :unprocessable_entity
end
When I run the test, the controller outputs "Failed to add user: {"username":["has already been taken"]}" as expected, but the test fails:
Expected response to be a <:unprocessable_entity>, but was <200>
However, with curl I get the response I expect:
curl -i -X POST -d '{"username": "james", "email": "test#test.com" }'
HTTP/1.1 422
{"username":["has already been taken"]}
So where am I going wrong with the assertion in the test?

You probably should use respond_with. It will take care of lots of REST logic, including setting status codes.
Also, Rails may be overkill for an application that's just a REST service. You might want to consider Sinatra instead.

The reason was that I'd switched to using RackTest to allow me to post JSON in the body (i.e. not as a form parameter.) As a result, I should have been making assertions on last_response (the RackTest MockResponse used in the post call) instead of using assert_response:
test "should not create user with duplicate username" do
#jim = users(:jim)
post '/users.json', #jim.to_json, "CONTENT_TYPE" => "application/json"
assert_status :unprocessable_entity
end
def assert_status(expected_status)
assert_equal last_response.status, Rack::Utils.status_code(expected_status)
end
I am quite surprised assert_response :success passes when no methods have been called which yield a value for #response. If that assertion failed, or threw an exception, it would have been easier to track down my bug, but c'est la vie!

Related

how to make a custom json rails routes and make the tests pass

Edit 2: OMG I AM SO STUPID. In my spec I have a let(:response) {MyModel.create()} so thats why its failing. Going to delete post
(edited for clarity)
In my routes file
root "search_email#index"
get "search_email/retrieve_last_user_survey" => "search_email#retrieve_last_user_survey"
Controller
class SearchEmailController < ApplicationController
def retrieve_last_user_survey
render :json => "")
end
end
Spec file
require "rails_helper"
RSpec.describe SearchEmailController, type: :controller do
describe 'GET #retrieve_last_user_survey' do
before do
get :retrieve_last_user_survey, :params => { :email => 'abc#abc.com'}
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
end
end
When try to run my test, i get this
Failure/Error: expect(response).to have_http_status(:success)
expected a response object, but an instance of Relational::Response (custom model name) was received
I have no idea why I am not getting a response object, I know I am hitting the controller method cause I inserted puts and I can see it.
Also on a semi related note. If i create a button that hits this route. why does it redirect me to a show route. I thought it would just return some http request that i can see in the dev console. I know cause said I dont have a show route or a show template.
It's not meant to be facetious, but to get the test to pass, replace the render line in the controller with:
head :ok
Does the test pass? Probably. So now add some expectation on the content header, and then finally the content itself.
If you break it down into small pieces, you should find the problem. It's not obvious from what you've shared, we can't see into the controller method.

Using Rspec to send post on create method

I am trying to use Rspec to send a post on my create action, I have simple controller logic. This is an api app, i run my rspec and it returns with a 422 status. I am trying to figure out whether or not my spec is sending information to the controller. These are the controller/specs in question, how do i insert information into the create method. So far all tutorials seem to be using the same syntax as mine. Thanks in advance
Ignore the hardcoded user_id in my factory, still learning factories and fixing dependency ones
#post_controller
def create
if User.exists?(params[:user_id])
#post = Post.new(post_params)
#post.save
render json: #post, status: 200
else
render status: 422
end
end
#spec
it "saves a new post in the database" do
attrs = attributes_for(:post)
post :create, post: attrs
expect(response.status).to eq(200)
end
#factory
FactoryGirl.define do
factory :post do
title "This is a new title"
body "This is the body"
user_id 1
end
Maybe is your user_id in params[:post][:user_id]?

Trying to pull data from params object in Rails API using JSON

With Rspec, I am trying to build a spec testing some basic http requests. I'm making a rookie mistake somewhere and need help finding it.
I am purposely making the spec fail with a nonsense expectation so the error message will tell me what I'm getting -- once I figure things out I'll correct the expectation:
user = create(:member)
json_data = {email: user.email, password: user.password}.to_json
post "api/v1/users/sign_in", json_data, format: :json
expect(last_response.body).to eq "foobar"
api/v1/users/sign_in routes to the following controller:
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params
end
end
This gives the error:
expected: "foobar"
got: "{\"{\\"email\\":\\"7abdiel_roob#smithrau.biz\\",\\"password\\":\\"12345678\\"}\"=>nil,
\"action\"=>\"create\", \"controller\"=>\"api/v1/sessions\"}"
Ok great. My data is getting to the server and the server sends it back, which is what I want. In my next step I try to grab the email. I change the controller to
class API::V1::SessionsController < Devise::SessionsController
respond_to :json
def create
render text: params[:email]
end
end
and I get
expected: "foobar"
got: " "
I looks to me that the params hash is using the JSOn data I sent in the request as the name of a key, not actually a value. Or maybe this is a strong_params thing? I've tried many things and can't seem to pull the data I want out of the params object.
What is happening is that you are double encoding the JSON data which you are sending in your spec.
json_data = {email: user.email, password: user.password}
post "api/v1/users/sign_in", json_data, format: :json
RSpec will automatically encode the request body as JSON for you.

Understanding Rails 3's respond_with

Utilizing ActionController's new respond_with method...how does it determine what to render when action (save) is successful and when it's not?
I ask because I'm trying to get a scaffold generated spec (included below) to pass, if only so that I can understand it. The app is working fine but, oddly, it appears to be rendering /carriers (at least that's what the browser's URL says) when a validation fails. Yet, the spec is expecting "new" (and so am I, for that matter) but instead is receiving <"">. If I change the spec to expect "" it still fails.
When it renders /carriers that page shows the error_messages next to the fields that failed validation as one would expect.
Can anyone familiar with respond_with see what's happening here?
#carrier.rb
validates :name, :presence => true
#carriers_controller.rb
class CarriersController < ApplicationController
respond_to :html, :json
...
def new
respond_with(#carrier = Carrier.new)
end
def create
#carrier = Carrier.new(params[:carrier])
flash[:success] = 'Carrier was successfully created.' if #carrier.save
respond_with(#carrier)
end
Spec that's failing:
#carriers_controller_spec.rb
require 'spec_helper'
describe CarriersController do
def mock_carrier(stubs={})
(#mock_carrier ||= mock_model(Carrier).as_null_object).tap do |carrier|
carrier.stub(stubs) unless stubs.empty?
end
end
describe "POST create" do
describe "with invalid params" do
it "re-renders the 'new' template" do
Carrier.stub(:new) { mock_carrier(:save => false) }
post :create, :carrier => {}
response.should render_template("new")
end
end
end
end
with this error:
1) CarriersController POST create with invalid params re-renders the 'new' template
Failure/Error: response.should render_template("new")
expecting <"new"> but rendering with <"">.
Expected block to return true value.
# (eval):2:in `assert_block'
# ./spec/controllers/carriers_controller_spec.rb:81:in `block (4 levels) in <top (required)>'
tl:dr
Add an error hash to the mock:
Carrier.stub(:new) { mock_carrier(:save => false,
:errors => { :anything => "any value (even nil)" })}
This will trigger the desired behavior in respond_with.
What is going on here
Add this after the post :create
response.code.should == "200"
It fails with expected: "200", got: "302". So it is redirecting instead of rendering the new template when it shouldn't. Where is it going? Give it a path we know will fail:
response.should redirect_to("/")
Now it fails with Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/carriers/1001>
The spec is supposed to pass by rendering the new template, which is the normal course of events after the save on the mock Carrier object returns false. Instead respond_with ends up redirecting to show_carrier_path. Which is just plain wrong. But why?
After some digging in the source code, it seems that the controller tries to render 'carriers/create'. There is no such template, so an exception is raised. The rescue block determines the request is a POST and there is nothing in the error hash, upon which the controller redirects to the default resource, which is the mock Carrier.
That is puzzling, since the controller should not assume there is a valid model instance. This is a create after all. At this point I can only surmise that the test environment is somehow taking shortcuts.
So the workaround is to provide a fake error hash. Normally something would be in the hash after save fails, so that kinda makes sense.

Why is this controller test on a create action failing?

I'm getting a failing test here that I'm having trouble understanding. I'm using Test::Unit with Shoulda enhancement. Action in users_controller.rb I'm trying to test...
def create
unless params[:user][:email] =~ / specific regex needed for this app /i
# ...
render :template => 'sessions/new'
end
end
Test...
context 'on CREATE to :user' do
context 'with invalid email' do
setup { post :create, { 'user[email]' => 'abc#abcd' } }
should_respond_with :success
end
# ...
end
Fails because "response to be a <:success>, but was <302>". How is it 302?
Change action to...
def create
render :template => 'sessions/new'
end
Test still fails.
#Ola: You're wrong: POST is connected to create. PUT is normally connected to update.
A :forbidden is quiet odd though. Here are some suggestions to find the problem (I've never used Shoulda, but I don't think it is a problem with Shoulda.
Make sure the route is defined in config/routes.rb and check with rake routes
Do you have any before_filters that could be responsible for that behaviour (login filter, acts_as_authenticated etc..)? Checkout log/test.log. A halt in the filter chain shows up there.
Print out the response body puts response.body to see what you get returned.
Hope this helps.
If you're using default REST-ful URLs, you probably should use PUT, not POST... Since PUT is connected to create, POST to that URL will give you an unauthorized and redirect.

Resources