This question already has an answer here:
How to test Controller post :create of JSON api on rails using rspec?
(1 answer)
Closed 4 years ago.
I have written an api for topics controller which will do all the crud operations.I need to test the api using Rspec. For the index action i have written a test case for http status.Further i need to check weather the index page is rendered correctly.Topic Api controller for index action is like this:
class Api::V1::TopicsController < ApplicationController
def index
#topics = Topic.all
render json: #topics,status: 200
end
end
Rspec for topic controller index action is:
RSpec.describe Api::V1::TopicsController do
describe "GET #index" do
before do
get :index
end
it "returns http success" do
expect(response).to have_http_status(:success)
////expect(response).to render_template(:index)
end
end
end
When run the testing it showing error message for the above line of code that i mentioned in comments.
Api::V1::TopicsController GET #index returns http success
Failure/Error: expect(response).to render_template(:index)
expecting <"index"> but rendering with <[]>
How to resolve it?
error:
TypeError: no implicit conversion of String into Integer
0) Api::V1::TopicsController GET #index should return all the topics
Failure/Error: expect(response_body['topics'].length).to eq(2)
TypeError:
no implicit conversion of String into Integer
You can test your API response for your controller actions, just a reference for your index action.
describe TopicsController do
describe "GET 'index' " do
it "should return a successful response" do
get :index, format: :json
expect(response).to be_success
expect(response.status).to eq(200)
end
it "should return all the topics" do
FactoryGirl.create_list(:topic, 2)
get :index, format: :json
expect(assigns[:topics].size).to eq 2
end
end
end
Related
I am new to RSpec but here I am trying to create tests based on this code and I am keep on getting this error. Any suggestions?
CODE:
serialization_scope nil
before_action :set_list, only: [:show, :destroy, :update]
before_action :verify_user, only: :show
def create
#list = current_user.lists.build(list_params)
if #list.save
render json: {message: ['Success']}, status: 200
else
render json: {errors:[#list.errors.full_messages]}, status: 400
end
end
Here is the RSpec file that I started :
require "rails_helper"
RSpec.describe V1::ListsController, :type => :controller do
describe "POST create" do
it "returns HTTP status" do
expect(post :create).to change(#list, :count).by(+1)
expect(response).to have_http_status :success #200
end
end
describe 'GET status if its not created' do
it "return HTTP status - reports BAD REQUEST (HTTP status 400)" do
expect(response.status).to eq 400
end
end
end
And the error that I got is :
Failures:
1) V1::ListsController GET status if its created returns HTTP status
Failure/Error: expect(post :create).to change(#list, :count).by(+1)
expected #count to have changed by 1, but was not given a block
# ./spec/controllers/lists_controller_spec.rb:8:in `block (3 levels) in <top (required)>'
2) GET status if its not created return HTTP status - reports BAD REQUEST (HTTP status 400)
Failure/Error: expect(response.status).to eq 400
expected: 400
got: 200
(compared using ==)
Try this code.
require 'rails_helper'
RSpec.describe V1::ListsController, type: :request do
describe 'valid request' do
it 'returns HTTP status' do
post '/list', params: { list: { list_name: 'xyz' } }
expect(response.status).to eq 201
end
end
describe 'invalid request' do
it "should return unauthorized" do
post '/list'
assert_response :unauthorized
end
end
end
In params you need to pass your list_params.
Spec would look like:
describe "POST create" do
context 'valid request' do
it 'should increase #list item' do
expect { post :create }.to change(List, :count).by(1)
end
it "returns HTTP status" do
post :create
expect(response).to have_http_status :success #200
end
end
context 'invalid request' do
it "return HTTP status - reports BAD REQUEST (HTTP status 400)" do
get :create
expect(response.status).to eq 400
end
end
end
Cheers!
You can test an object not being created by intentionally causing some of its validations to fail e.g. you can pass a mandatory attribute as nil from the RSpec.
Sample request: post :create, { title: nil }.
But as per your RSpec code, it seems there are no validations on List model. So, lets try to stub save and return false for this particular test.
describe 'GET status if its not created' do
# Assuming your model name is `List`
before { allow_any_instance_of(List).to receive(:save) { false } }
it "return HTTP status - reports BAD REQUEST (HTTP status 400)" do
post :create
expect(response.status).to eq 400
end
end
Please post your model for list and i can update the answer with more appropriate test.
Ishika, let me see if I can help you :)
RSpec official documentation recommends you to use request specs instead of controller specs. That is recommended because Rails 5 deprecated some methods used on controller testings. You can read more about this here at RSpec blog
ps.: You can use controller tests so far, but it can be deprecated in a future major version of RSpec.
There are some notes I left after the code, please read them also.
I would write a request spec like this:
# spec/requests/v1/lists_controller_create_spec.rb
require "rails_helper"
RSpec.describe V1::ListsController do
describe 'success' do
it 'returns ok and creates a list', :aggregate_failures do # :aggregate_failures is available only for RSpec 3.3+
expect do
post '/list', title: 'foo' # This will also test your route, avoiding routing specs to be necessary
end.to change { List.count }.from(0).to(1)
expect(response).to have_http_status(:ok)
end
end
describe 'bad request' do
before do
# This is needed because your controller is not validating the object, but look at my
# comment below (out of the code), to think about this behavior, please.
allow_any_instance_of(List).to receive(:save).and_return(false)
end
it 'returns a bad request and does not create a list' do
expect do
post '/list', title: 'foo' # This will also test your route, avoiding routing specs to be necessary
end.not_to change { List.count }
expect(response).to have_http_status(:bad_request)
end
end
end
Notes:
I suggested using more than 1 expectation by example, that is ok in this spec because they are simple and because I'm using :aggregate_failures option. With this option, if the first expectation fails, the next expectations will also be executed, considering that in this case, the following expectations does not depend on the first one, it is ok to use more than 1 expectation for the example.Reference
You are returning a bad request if the object is not saved, but you are not validating it. If your model has validations that will validate the object there, please adjust the specs to fail the save (instead of using the mock I used) and consider rendering an error message in the response
If you think that making the post inside a expect block, you can do different: Store the count of Lists in a variable before making the post and after the post you test if the variable has changed or not, maybe you think it will be more clear and it will do exactly the same thing in the background.
I have a following method in my controller:
def create
#job = Job.new(job_params)
if #job.save
render 'create_success', status: :created
else
render 'create_failure', status: :bad_request
end
end
And here are my controller specs for this:
require 'rails_helper'
RSpec.describe Api::V1::JobsController, :type => :controller do
let(:actor) { FactoryGirl.create(:user, :service_advisor) }
let(:auth_token) { actor.authentication_token }
describe 'POST #create' do
render_views
context 'with invalid attrubutes' do
let(:job) { FactoryGirl.build(:job, customer_id: nil).attributes }
it 'renders create_failure view' do
expect(response).to render_template :create_failure
end
it 'returns 400 status code' do
expect(response.status).to eq 400
end
end
end
end
However, for some reason, no matter what i do, the controller spec allways thinks that this method returns empty response and status 200. I can test that method using postman or curl and it works as expected - return error messages and status codes (201/400) but my spec allways sees it as empty response with 200 status code.
It seems that render_views is not working. My env is:
ruby 2.0.0p594
Rails 4.1.0
rspec 3.1.5
You have not given call to create action in your test case.
it 'renders create_failure view' do
post :create, job
expect(response).to render_template :create_failure
end
Working on controller testing and wanted to test that when I go to the index page, I should see the total number of users created and that should equal all the users that were in fact created. Can't get it to work and no errors are coming up, it just freezes and I have to press control c to exit.
describe "GET #index" do
it "show a list of all users" do
total = User.all.count
get :index
expect(response).to eq total
end
rspec controller tests don't render views by default, testing success might be better start
describe "GET #index" do
it "show a list of all users" do
get :index
expect(response).to be_success
end
end
If you really want to check rendering
describe "GET #index" do
render_views
it "show a list of all users" do
total = User.all.count
get :index
expect(response).to contain total.to_s
# OR
expect(response.body).to match total.to_s
end
end
see: https://www.relishapp.com/rspec/rspec-rails/v/2-2/docs/controller-specs/render-views
If you want check displaying of some information on page, it will be better to write integrations test using Capybara.
Purpose of controller tests is to check incoming parameters, variables initialized in controller and controller response (rendering views or redirecting...).
About your question - if you have next controller:
class UsersController < ApplicationController
def index
#users = User.all
end
end
you can write next controller test:
describe UsersController do
it "GET #index show a list of all users" do
User.create(email: 'aaa#gmail.com', name: 'Tim')
User.create(email: 'bbb#gmail.com', name: 'Tom')
get :index
expect(assigns[:users].size).to eq 2
end
end
I am attempting to create a RSpec controller test for a namespaced controller, but rspec doesn't seem able to detect the nesting and generate the proper path for the post :create action.
This is my spec code:
# for: /app/controllers/admin/crm/report_adjustments_controller.rb
require 'spec_helper'
describe Admin::Crm::ReportAdjustmentsController do
render_views
before(:each) do
signin
end
describe "GET 'index'" do
it "returns http success" do
get :index
response.should be_success
end
end
describe "POST 'create'" do
it "creates with right parameters" do
expect {
post :create, report_adjustment: {distributor_id: #ole_distributor.id, amount: "30.0", date: Date.today }
}.to change(Crm::ReportAdjustment, :count).by(1)
response.should be_success
end
end
end
# routes.rb
namespace :admin do
namespace :crm do
resources :report_adjustments
end
end
For this code, the get :index works just fine, but when post :create is called, the following error is generated: undefined method 'crm_report_adjustment_url'
Why would RSpec be smart enough to figure things out with get :index, but not with post :create? How do I get RSpec to properly load the right route, which is admin_crm_report_adjustments_url?
Thanks in advance.
Try posting to the url instead:
post admin_crm_report_adjustments_url
# or
post "/admin/crm/report_adjustments"
I want to put the common controller actions, index, show, create etc. in the ApplicationController like this:
class ApplicationController < ActionController::Base
respond_to :json
def index
#implementation
end
def show
#implementation
end
def update
#implementation
end
end
The app will only return JSON.
I have written the following spec to test this with RSPEC's annonymous controller
describe ApplicationController do
controller do ; end
describe 'Get :index' do
it 'should respond to index' do
get :index
response.code.should eq "200"
end
end
end
The above spec gives the following error:
ActionView::MissingTemplate: Missing template anonymous/index,
application/index with {:locale=>[:en], :formats=>[:json],
:handlers=>[:erb, :builder]}. Searched in: *
"#"
Can anyone suggest a way to make this work with the anonymous controller?
try this may be it helpful
your controller like
def index
end
your rspec testing like
describe "GET index" do
it "should respond to index" do
get :index
response.code.should eq "200"
end
end
create index.html.erb in your application/ folder
then test it.
describe "GET index" do
it "returns correct JSON" do
# #groups.should have(2).items
get :index, :format => :json
response.should be_success
body = JSON.parse(response.body)
body.should include('group')
groups = body['group']
groups.should have(2).items
groups.all? {|group| group.key?('customers_count')}.should be_true
groups.any? {|group| group.key?('customer_ids')}.should be_false
end
end