How do we print response body in rspec - ruby-on-rails

I am trying to write an rspec file for my meetings_controller.rb so as to check if the values returned from my database are correct.
When I go to localhost:3000/meeting.json, this is the result of my data from the database
my rspec file is trying to check if the correct values are returned.
I created a folder called controller under specs (after I have installed rspec)and have the file meeting_controller_spec.rb
require 'rails_helper'
# Change this ArticlesController to your project
RSpec.describe MeetingsController, type: :controller do
describe "GET #index" do
# check index
it "returns a success response" do
get :index
puts response.body
expect(response).to have_http_status(:ok)
end
end
end
I tried to print the response body but nothing is returning. Is there a way to do this?
(base) adam-a01:reservation adrianlee$ rspec
.
Finished in 0.0892 seconds (files took 12.15 seconds to load)
1 example, 0 failures
Btw this is my meeting_controller.rb
class MeetingsController < ApplicationController
before_action :set_meeting, only: [:show, :edit, :update, :destroy]
# GET /meetings
# GET /meetings.json
def index
#meetings = Meeting.all
#meeting = Meeting.new
end
end
Update: I also tried this method as suggested below but it didnt work still
# Change this ArticlesController to your project
RSpec.describe MeetingsController, type: :controller do
describe "GET #index" do
# check index
it "returns a success response" do
get :index
raise response.body
expect(response).to have_http_status(:ok)
end
end
end
This is the error raised
Failures:
1) MeetingsController GET #index returns a success response
Failure/Error: raise response.body
RuntimeError:
# ./spec/controllers/meeting_controller_spec.rb:10:in `block (3 levels) in <top (required)>'

If you are trying to get this value for a debugging purpose you should use byebug or pry gems.
If you choose byebug, add it to your gemfile gem 'byebug' on test environment.
run bundle install and after that you are able to use it on your tests
So replace the puts with byebug
describe "GET #index" do
# check index
it "returns a success response" do
get :index
byebug
expect(response).to have_http_status(:ok)
end
end
At the console now you are exactly there. Just type response.body and enter to print it's value.
When you are done, just type c and then enter to release the console e continue your tests.
Also check here for more information about debugging with byebug

In my case I do it like this:
require 'rails_helper'
describe SuppliersController, type: :controller do
describe 'GET /suppliers' do
let!(:access_token) { create :access_token }
let!(:admin_user_token) do
create :user_token, resource_owner: create(:admin_user)
end
context 'when the requester is an admin' do
it 'returns HTTP status 200 (OK)' do
allow(controller).to receive(:doorkeeper_token) { access_token }
#request.env['HTTP_X_USERTOKEN'] = admin_user_token.token
get :index
raise response.body.inspect ## Here is what prints the value in the console.
body = JSON.parse response.body
expect(response).to have_http_status :ok
expect(body['status']).to eq 'OK'
end
end
end
end
To run the test:
rspec spec/controllers/suppliers/suppliers_controller_index_spec.rb
And it gives me the output:
F
Failures:
1) SuppliersController GET /suppliers when the requester is an admin returns HTTP status 200 (OK)
Failure/Error: raise response.body.inspect
RuntimeError:
"{\"status\":\"OK\",\"message\":\"Your request has been processed successfully.\",\"data\":[],\"page\":{\"current_page\":1,\"prev_page\":null,\"next_page\":null,\"per_page\":25,\"total_pages\":0}}"
# ./spec/controllers/suppliers/suppliers_controller_index_spec.rb:17:in `block (4 levels) in <top (required)>'
Finished in 0.47594 seconds (files took 2.82 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/suppliers/suppliers_controller_index_spec.rb:11 # SuppliersController GET /suppliers when the requester is an admin returns HTTP status 200 (OK)

I have to say raise a runtime error to check variable value is not a good practice,maybe you should try gem 'pry' or gem 'byebug' suggested by #DR7.
Another thing you need make sure is did you run rails s and rspec in different environment?This maybe lead to they connect to separate db.

Related

Rails rspec wrong expected result

I'm having problem with using rspec in ruby-on-rails app
my users_spec.rb
require 'rails_helper'
RSpec.describe 'Get /', type: :request do
it 'test / get method' do
expect(response).to have_http_status(:success)
expect(response).to include('index')
end
end
my routes.rb
Rails.application.routes.draw do
get "/" => "users#index"
end
my users_controller.rb
class UsersController < ApplicationController
def index
end
end
my index.erb
<h1>index</h1>
when I run "bundle exec rspec". , I get error like this
.F
Failures:
1) Get / test / get method
Failure/Error: expect(response).to have_http_status(:success)
expected the response to have a success status code (2xx) but it was
# ./spec/requests/users_spec.rb:5:in `block (2 levels) in <main>'
Finished in 0.11204 seconds (files took 5.18 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/requests/users_spec.rb:4 # Get / test / get method
and I can't catch where is wrong..
Your spec is not correct. Your spec is not actually calling the endpoint. You need to add a get "/" call, that will actually call the endpoint
require 'rails_helper'
RSpec.describe 'Get /', type: :request do
it 'test / get method' do
get "/" #this is the missing line; this actually makes the request
expect(response).to have_http_status(:success)
expect(response).to include('index')
end
end

Using rspec to test an API endpoint

I have this api endpoint wot get all the blogs from my database that works id the user pass an api_key. This works correctly and now I'm trying to testing this endpoint.
Routes:
Rails.application.routes.draw do
get 'blogs', to: 'blogs#index'
end
Blogs controller:
class BlogsController < ApplicationController
def index
if params[:api_key]
user = User.find_by(api_key: params[:api_key])
if user.present?
#blogs = Blog.all
return render json: #blogs, status: :ok
end
end
render json: { error: "Unauthorized!" }, status: :bad_request
end
end
I'm new to rspec and tests in general, I watched a couple videos and tutorials and this is what I have so far:
spec/requests/blogs_spec.rb
require 'rails_helper'
RSpec.describe 'Blogs API', type: :request do
let!(:blogs) { Blog.limit(10) }
describe 'GET /blogs' do
before { get '/blogs' }
it 'returns status code 400' do
expect(response).to have_http_status(400)
end
context 'when the request is valid' do
before { get '/blogs', params: { api_key: '123123'} }
it 'returns status code 400' do
expect(response).to have_http_status(200)
end
end
end
end
I can't seem to make the last test work and I don't know why. My guess is that I'm not passing api_key correctly, but I don't know how
1) Blogs API GET /blogs when the request is valid returns status code 400
Failure/Error: expect(response).to have_http_status(200)
expected the response to have status code 200 but it was 400
# ./spec/requests/blogs_spec.rb:28:in `block (4 levels) in <top (required)>'
Ok, so accordingly to your question + comments, I can assume you are running your tests within test environment, but you are expecting to find a User existing in development database.
FactoryBot
You might wanna use FactoryBot to create records for your testing suite.
Add to your Gemfile:
group :development, :test do
gem 'factory_bot_rails'
end
In rails_helper.rb, add:
RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
end
Now you should create your User factory. Create a new file spec/factories/user.rb with the following:
FactoryBot.define do
factory :user do
api_key { '123123' }
# You should define every any other required attributes here so record can be created
end
end
Finally, in your spec file:
....
context 'when the request is valid' do
before { get '/blogs', params: { api_key: user.api_key} }
let!(:user) { create(:user) }
it 'returns status code 200' do
expect(response).to have_http_status(200)
end
end
...
Now your test should pass. Notice that in testing database there is no Blog created also, so:
let!(:blogs) { Blog.limit(10) }
Will return an empty array. You will need to create a Blog factory too, and create blogs like:
let!(:blogs) { create_list(:blog, 2) }
Bonus
As soon as you start improving your tests, you may wanna take a look at Faker and Database Cleaner for ActiveRecord

Rspec for conditional code if-else?

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.

Testing XmlBuilder with Rspec

I posted a question (Rails XML builder not rendering) regarding rendering XML using XmlBuilder. That particular issue was resolved but I’ve run into another issue testing this controller action.
The issue seems to be with Rspec as because the same test written in Minitest works fine and using cURL works too.
While debugging this, Rspec never tries to render the xml builder template. It just skips over the template and renders a http status 200 with a blank body.
Here are the controller, xml builder, tests, and test results:
controllers/bars_controller.rb
require 'builder'
class BarsController < ApplicationController
def show
#bar = Bar.find(params[:id])
render template: 'bars/show.xml.builder', formats: [:xml]
end
end
/views/bars/show.xml.builder
xml.instruct!
xml.bar do
xml.foo(#bar.foo)
xml.bar(#bar.bar)
end
/test/controllers/bars_controller_test.rb
require 'test_helper'
class BarsControllerTest < ActionController::TestCase
setup do
#bar = Bar.create(foo: 'bar', bar: 'foo')
end
test "should show bar" do
get :show, id: #bar
assert_response :success
assert_match "<bar>", response.body
end
end
/spec/controllers/bars_controller_spec.rb
require_relative '../rails_helper'
describe BarsController do
describe 'a POST to :show' do
before do
#bar = Bar.create(foo: 'bar', bar: 'foo')
post :show, id: #bar
end
it 'should show bar' do
expect(response).to be_success
expect(response.body).to include("<bar>")
end
end
end
Test results
> rake test
Run options: --seed 50688
# Running:
.
Finished in 0.096901s, 10.3198 runs/s, 30.9593 assertions/s.
1 runs, 3 assertions, 0 failures, 0 errors, 0 skips
> rspec spec/controllers/bars_controller_spec.rb
F
Failures:
1) BarsController a POST to :show responds with xml bar
Failure/Error: expect(response.body).to include("<bar>")
expected "" to include "<bar>"
# ./spec/controllers/bars_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
Finished in 0.01726 seconds (files took 1.53 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/bars_controller_spec.rb:11 # BarsController a POST to :show responds with xml bar
RSpec controller tests do not render views by default. In order to render the views, add render_views to the describe block:
describe 'a POST to :show' do
render_views
before do
#bar = Bar.create(foo: 'bar', bar: 'foo')
post :show, id: #bar
end
it 'should show bar' do
expect(response).to be_success
expect(response.body).to include("<bar>")
end
end

Expect mock to receive message in rails controller

I'm having trouble setting expectations for a mock used in one of my controllers:
controllers/blark_controller.rb
class BlarkController < ApplicationController
def show
user = User.first
user.inspect
render nothing: true
end
end
spec/controllers/blark_controller_spec.rb
require 'spec_helper'
describe BlarkController do
describe 'GET :show' do
let(:user) { mock_model User }
before do
User.stub(:first).and_return(user)
get :show
end
it 'blarks' do
expect(user).to receive(:inspect)
end
end
end
Results in this:
22:04:58 - INFO - Running: spec/controllers/blark_controller_spec.rb
BlarkController
GET :show
blarks (FAILED - 1)
Failures:
1) BlarkController GET :show blarks
Failure/Error: expect(user).to receive(:inspect)
(Double "User_1001").inspect(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/controllers/blark_controller_spec.rb:14:in `block (3 levels) in <top (required)>'
Finished in 0.15579 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/blark_controller_spec.rb:13 # BlarkController GET :show blarks
Can I set expectations on mocks in specs?
You can, but the way you're doing it is wrong.
You're calling an action (get :show) and then after calling it, setting a future expectation (expect(user).to receive(:inspect)). Obvious this won't work because you've already called the action, there is no future for this test.
You either need to set the expectation before calling the action (switch the order of the statements) or use rspec's recently-added spies feature to set expectations after the fact. This uses have_received rather than receive.
For more detail: https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/spies/spy-on-a-stubbed-method-on-a-partial-mock

Resources