Testing XmlBuilder with Rspec - ruby-on-rails

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

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

How do we print response body in rspec

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.

How to stub WickedPdf?

I'm using the wicked_pdf gem, and I'm currently trying to speed up my spec suite.
I realised that some of my specs are generating PDF in my tmp folder, which is quite time-consuming.
Is there any way to completely stub the wicked_pdf generation, so it don't actually generate the PDF?
It depends on how you are using it, and how much you want to cut out during your test suite, but probably a good place is WickedPdf::PdfHelper#make_pdf, which you could stub with something like this in an RSpec test:
describe MyController do
describe '#show.pdf' do
it 'creates a PDF'
let(:tiny_pdf) do
# What is the smallest possible valid PDF?
# https://stackoverflow.com/a/17280876/23915
"%PDF-1.4\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>"
end
# Here is the actual stubbing
allow(WickedPdf::PdfHelper).to receive(:make_pdf).and_return tiny_pdf
get :show, params: { id: 1, format: :pdf }
expect(response.status).to eq 200
end
end
end
Or you could override it globally by reopening the class and changing the definition:
if Rails.env.test?
class WickedPdf
class PdfHelper
def make_pdf(options = {})
"%PDF-1.4\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>"
end
end
end
end
Stubbing out WickedPdf::PdfHelper#make_pdf as in the other answer prevents testing what's being rendered with e.g. expect(response).to render_template("show").
To find which methods lead to writing to files I temporarily added expect(File).not_to receive(:open) before the get ... format: :pdf and found:
Failure/Error: render pdf: "...", template: "show"
(File (class)).open("/tmp/wicked_pdf20200304-24076-r7r1eh.html", 194, {:perm=>384})
expected: 0 times with any arguments
received: 1 time with arguments: ("/tmp/wicked_pdf20200304-24076-r7r1eh.html", 194, {:perm=>384})
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf/tempfile.rb:10:in `initialize'
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf.rb:58:in `new'
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf.rb:58:in `pdf_from_string'
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf/pdf_helper.rb:91:in `make_pdf'
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf/pdf_helper.rb:113:in `make_and_send_pdf'
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf/pdf_helper.rb:40:in `render_with_wicked_pdf'
# /usr/local/bundle/gems/wicked_pdf-1.4.0/lib/wicked_pdf/pdf_helper.rb:30:in `render'
# ./app/controllers/reports_controller.rb:21:in `block (2 levels) in show'
# ./app/controllers/reports_controller.rb:11:in `show'
I then experimented with stubbing at different places in this backtrace and found that the following makes the test as fast as the usual "html" controller tests while still allowing to test for render_template("show"):
it "returns http success" do
allow_any_instance_of(WickedPdf::PdfHelper).to receive(:make_and_send_pdf)
get :show, params: { id: 1, format: :pdf }
expect(response).to have_http_status(:success)
end
it "renders the show template" do
allow_any_instance_of(WickedPdf::PdfHelper).to receive(:make_and_send_pdf)
get :show, params: { id: 1, format: :pdf }
expect(response).to render_template("show")
end
this also makes returning tiny pdf unnecessary.

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 if a controller renders a JS Template

I have this controller:
class UsersController < ApplicationController
def enable_password_change
respond_to do |format|
format.js {
render :layout => false
}
end
end
end
And the following test:
test "should render js to show the change password form" do
sign_in_user
request_javascript
get :enable_password_change
assert_template :enable_password_change
assert_response :ok
end
And the helper method to set the request headers:
def request_javascript
#request.headers["Accepts"] = "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
end
However my test reports:
Finished tests in 0.942294s, 7.4287 tests/s, 15.9186 assertions/s.
1) Error:
UsersControllerTest#test_should_render_js_to_show_the_change_password_for:
ActionController::UnknownFormat: ActionController::UnknownFormat
app/controllers/users_controller.rb:9:in `enable_password_change'
test/controllers/users_controller_test.rb:14:in `block in <class:UsersControllerTest>'
7 tests, 15 assertions, 0 failures, 1 errors, 0 skips
What am I doing wrong here?
I found the the xml_http_request or alias xhr for that
test "should render js to show the change password form" do
sign_in_user
xhr :get, :enable_password_change
assert_template :enable_password_change
assert_response :ok
assert assigns(:merchant_user)
end
In an instance of TestCase, #get calls #process which calls #request.recycle! which sets #request.headers to an empty hash. To test a js response, I think you'll have to have the test fire up a browser with Selenium or something like that.

Resources