I'm having a perplexing problem where my controller is working fine. However, when I'm testing it with RSPEC it's returning an empty string as the response body.
Here is the controller:
class Api::UsersController < Api::ApplicationController
def show
#user = User.find(params[:id])
render 'show', status: 200
# render json: #user
end
end
And the RABL template I'm rendering:
object #user
attributes :id, :name, :email, :phone_number, :invite_token
Finally here is my spec:
require "spec_helper"
describe Api::UsersController do
it "returns user attributes" do
user = FactoryGirl.create(:user, name: "Mark", email: "foo#bar.com")
get :show, id: user.id
expect(response.status).to eq(200)
output = JSON.parse(response.body)
end
end
When I use render 'show' to render the RABL template my test fails as the response.body is an empty string. However, if I CURL to that endpoint, the body returns just fine.
When I change the controller to: render json: #user the test passes.
Can anyone tell me what's going on here?
Thanks in advance!
try to add render_views at the top of the tests
describe Api::UsersController do
render_views
it "returns user attributes" do
user = FactoryGirl.create(:user, name: "Mark", email: "foo#bar.com")
get :show, id: user.id
output = JSON.parse(response.body)
expect(response.status).to eq(200)
expect(output).to eq(expected_hash)
end
end
Possible reason: RSpec do not render views by default to speed up tests.
Related
I'm new to rails api rspec and somehow I cannot make the test work. Can someone provide some inputs in the tests? The models and controllers are more like a pseudocode. I appreciate it. Thank you.
# routes.rb
Rails.application.routes.draw do
resources :users, :only [:create]
end
# app/model/user.rb
class User < ApplicationRecord
validates_uniqueness_of :name
end
# app/controllers/users_controller.rb
def create
#user = User.new(user_params)
if #user.save
head 200
else
render json: { error: 'Failed', status: 400}, status: 400
end
end
def user_params
params.require(:user).permit(:name)
end
# RSpec Test
require 'rails_helper'
RSpec.describe UsersController do
describe '#create' do
context 'the parameter "user[name]"" is blank' do
it 'creates new user' do
#Test Here
end
it 'renders empty response' do
#Test Here
end
it 'renders response with status 200' do
#Test Here
end
end
end
end
spec/models/user_spec.rb make sure you install gem shoulda-matchers and factory_bot_rails
require 'rails_helper'
RSpec.describe User, type: :model do
let(:user) { create(:user) }
describe "validation" do
it { should validate_uniqueness_of(:name) }
end
end
spec/controllers/users_controller_spec.rb
require 'rails_helper'
RSpec.describe UserssController, type: :controller do
describe "POST /users" do
it "when create user successfully return status 200" do
post :create, params: { name: "name" }
expect(response.status).to eq 200
end
it "when create user errors return status 400" do
post :create, params: { name: "name duplicate" }
expect(response.status).to eq 400
end
end
end
I hope it helps for you
I am trying to stub out an :authenticate_user method call in my request spec so I can test the user's association creation. I am using these blog posts as a guide on stubbing:
1) https://8thlight.com/blog/mike-knepper/2014/07/01/stubbing-authentication-and-authorization-in-controller-specs.html
2) http://johnnyji.me/rspec/2015/06/18/stubbing-controller-instance-methods-in-rspec.html
I'm not having any success with stubbing and I can't figure out what am I missing.
When I tried
it 'creates a new contract' do
allow(controller).to receive(:authenticate_user).and_return(user)
post api_v1_user_contracts_path(user), { params: contract_params}
expect(response).to have_http_status(200)
end
I got:
When I tried:
it 'creates a new contract' do
allow_any_instance_of(controller).to receive(:authenticate_user).and_return(user)
post api_v1_user_contracts_path(user), { params: contract_params}
expect(response).to have_http_status(200)
end
I got
My code:
spec/requests/contracts_api_spec.rb
require 'rails_helper'
require 'pry'
context "POST #create" do
let (:user) { User.create(full_name: "Jason Bourne", email: "jbourne#test.com", password: "123456") }
let (:contract_params) do
{
"contract[vendor]" => "Lebara",
"contract[starts_on]" => "2018-12-12",
"contract[ends_on]" => "2018-12-16",
"contract[price]" => "15"
}
end
it 'creates a new contract' do
allow(controller).to receive(:authenticate_user).and_return(user)
post api_v1_user_contracts_path(user), { params: contract_params}
expect(response).to have_http_status(200)
end
app/controllers/api/v1/contracts_controller.rb
class Api::V1::ContractsController < ApplicationController
before_action :authenticate_user
def show
if #current_user.contracts.find_by(id: params[:id])
render json: #current_user.contracts.find_by(id: params[:id])
else
render json: { error: "Contract not found"}, status: 400
end
end
def create
contract = #current_user.contracts.build(contract_params)
if contract.save
render json: contract
else
render json: { error: contract.errors }, status: 400
end
end
app/controllers/concerns/token_authenticatable.rb
class NotAuthorizedException < StandardError; end
module TokenAuthenticatable
extend ActiveSupport::Concern
included do
attr_reader :current_user
before_action :authenticate_user
rescue_from NotAuthorizedException, with: -> { render json: { error: 'Not Authorized' }, status: :unauthorized }
end
private
def authenticate_user
#current_user = DecodeAuthenticationCommand.call(request.headers).result
raise NotAuthorizedException unless #current_user
end
end
Additional questions:
1) Should I be using a real User object, or should that be a double? I'm assuming it should be a real user in order to test if the association creation is working.
2) Should I be using allow(Api::V1::ContractsController).to receive(:authenticate_user).and_return(user)? I've tried it before and didn't work but I didn't know it was because something else also was breaking it.
Thanks for any feedback you can give!
The point is that authenticate_user assigns user to the variable (and you use it later). Please try:
allow(DecodeAuthenticationCommand).to receive_message_chain(:call, :result).and_return(user)
With the test double, you will have to define all methods for the user, such as contracts. Also, you are checking if the contract was created - in my opinion, it is perfectly fine to use a real object for the user.
Here is my create action for users:
def create
#user = User.new(user_params)
if #user.save
respond_to do |format|
format.html {
redirect_to edit_admin_user_path(#user)
flash[:success] = "Successfully created"
}
end
else
render :new
flash[:alert] = "Something went wrong"
end
end
My test is looking like this:
context "POST methods" do
describe "#create" do
it "renders the edit template" do
post :create, user: FactoryGirl.attributes_for(:user)
expect(response).to render_template(:edit)
end
end
end
However I'm getting this error:
Failures:
1) Admin::UsersController POST methods #create renders the edit template
Failure/Error: expect(response).to render_template(:edit)
expecting <"edit"> but was a redirect to <http://test.host/admin/users/80/edit>
I want to check if the edit.html.haml file is rendered after creating a user. What am I doing wrong?
Update #1
I do check for redirect in another test, this is my full test suite:
require 'rails_helper'
RSpec.describe Admin::UsersController, type: :controller do
render_views
context "POST methods" do
describe "#create" do
it "using valid params" do
expect{
post :create, user: { email: "something#hello.com", password: "long12345678" }
}.to change(User, :count).by(1)
# get user_path('1')
end
it "redirects to the edit page after saving" do
post :create, user: FactoryGirl.attributes_for(:user)
user = User.last
expect(response).to redirect_to(edit_admin_user_path(user.id))
end
it "renders the edit template" do
post :create, user: FactoryGirl.attributes_for(:user)
user = User.last
expect {
redirect_to(edit_admin_user_path(user.id))
}.to render_template(:edit)
end
context "it redirects to new" do
it "if user has no valid mail" do
post :create, user: { email: "something", password: "long12345678" }
expect(response).to render_template(:new)
end
it "if user has no valid password" do
post :create, user: { email: "something#mail.com", password: "short" }
expect(response).to render_template(:new)
end
end
end
end
end
What I want is to actually check if the edit template is rendered. Because with expect(response).to redirect_to(edit_admin_user_path(user)) it does not check the template. This test passes even if I have no edit.html.haml file at all.
When you're testing create action you should just check correctness of redirect. In this action you're not actually rendering edit template, but you're just making redirect to the edit path of created entity. So this is the thing you should check.
describe "#create" do
it "redirects to the edit path" do
post :create, user: FactoryGirl.attributes_for(:user)
expect(response).to redirect_to(edit_admin_user_path(User.last))
end
end
Then you should have another test for edit action, where you're checking template rendering. That will mean that after redirect in create action you also will see the proper template.
You are redirecting to edit_admin_user_path after successfully saving the User in your controller action. But, you're testing render in the test instead.
Update your test as below.
context "POST methods" do
describe "#create" do
before(:each) do
#user = FactoryGirl.attributes_for(:user)
end
it "renders the edit template" do
post :create, user: #user
expect(response).to redirect_to(edit_admin_user_path(#user))
end
end
end
so i've been trying to make a basic response test - with 200 and 403. I'm not sure i need to add anything else ..
accounts_spec.rb
RSpec.describe Api::V1::AccountsController, :type => :controller do
describe "GET index no account" do
it "has a 403 status code" do
get :index
expect(response.status).to eq(403)
end
end
describe "GET index with account" do
login_user
it "has a 200 status code" do
get :index
expect(response.status).to eq(200)
end
end
end
Accounts Controller #index
def index
#show user details
raise if not current_user
render json: { :user => current_user.as_json(:except=>[:created_at, :updated_at, :authorization_token, :provider, :uid, :id])}
rescue
render nothing: true, status: 403
end
I keep getting
1)Api::V1::AccountsController GET index with account has a 200 status code
expected: 200
got: 403
Any thoughts on where i'm doing it wrong ?
UPDATE
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
sign_in :user, user
end
end
end
Much cleaner implementation
class SomeController < ApplicationController
before_action :authenticate
skip_before_action :verify_authenticity_token, if: :json_request?
def index
render json: { user: current_user...
end
protected
def json_request?
request.format.json?
end
def authenticate
head :unauthorized unless current_user
end
end
I also recommend in using ActiveModel Serializer https://github.com/rails-api/active_model_serializers. This will separate logic of render json and oy will have a separate class under serializer that defines the json output. So your render method will look like this:
render json: current_user, status: :ok
app/serializers/user.rb
class UserSerializer < ActiveModel::Serializer
attribute :id, :email #list your attributes for json output
end
If you want to test json response in your rspec what I find best is testing against json schema like this library https://github.com/sharethrough/json-schema-rspec.
Hope it helps
I have written this controller code in Ruby on Rails
class PostsController < ApplicationController
before_filter :authenticate_user!
def index
#posts = Post.all(:order => "created_at DESC")
respond_to do |format|
format.html
end
end
def create
#post = Post.create(:message => params[:message])
respond_to do |format|
if #post.save
format.html { redirect_to posts_path }
format.js
else
flash[:notice] = "Message failed to save."
format.html { redirect_to posts_path }
end
end
end
end
and corresponding to this I have written the following test case :-
require 'spec_helper'
describe PostsController do
describe "GET 'index'" do
it "returns http success" do
get 'index'
response.should be_success
end
end
describe "#create" do
it "creates a successful mesaage post" do
#post = Post.create(message: "Message")
#post.should be_an_instance_of Post
end
end
end
I am getting failures on both. Please take a look on the code and help me figure out.
I suspect you are not logged in since you are using Devise?
Maybe you need to include the devise testhelpers:
describe PostsController do
include Devise::TestHelpers
before(:each) do
#user = User.create(...)
sign_in #user
end
#assertions go here
end
As Tigraine states, it appears as though you probably are not logged in (with Devise) when the tests get executed. However, showing the failures would help in narrowing down the problem further.
On top of that, the second test isn't really an integration test and I would probably prefer something like the following to test the same condition. There are two types of test you could do:
# inside 'describe "#create"'
let(:valid_params) { {'post' => {'title' => 'Test Post'} }
it 'creates a new Post' do
expect {
post :create, valid_params
}.to change(Post, :count).by(1)
end
# and / or
it 'assigns a new Post' do
post :create, valid_params
assigns(:post).should be_a(Post)
assigns(:post).should be_persisted
end
Don't forget to add this line into your spec_helper.rb
require "devise/test_helpers"
include Devise::TestHelpers
Nevertheless, here is link for Devise wiki - How to test Controllers where you can find more info about this approach. I recommend writing the before method without (:each), what I remember it sometimes causes problems.
before do
#user = FactoryGirl.create(:user)
sign_in #user
end
Can always use:
puts response.inspect
To see how your response looks like.