Rspec test on response should be successful fails - ruby-on-rails

I'm running this test on rspec...
it 'get email is successful' do
get :email
response.should be_success
response.should render_template('email')
end
Where the controller code looks like this..
def email
respond_to do |format|
format.js
end
end
On my terminal I'm returning with....
3) PostsController checking to see if response for post email is successful
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/posts_controller_spec.rb:116:in `block (3 levels) in <top
(required)>'
What am I missing here to make the test work? It has to be something obvious. My view file is titled email.js.erb. This action is meant for an AJAX call.

try:
xhr :get, :email
your request seems not to take into account the expected response type. In Rspec, this is the way to do it if you want to simulate AJAX (XHR) requests.

Your controller is structured to return anything only for JS requests and you are requesting HTML response in your tests. You should have
get :email.js
in your spec.

Related

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.

How do I check both rendering :new and JSON response?

I try to add RSpec test for invalid entry create controller action, which responds to HTTP POST verb.
it "case of invalid entry" do
program=FactoryGirl.attributes_for(:program, :faculty_id => faculty.id, :code=>"воруй&убивай")
post :create, {:program=>program, :faculty_shortcut=>faculty }
expect(response).to render_template(:new)
end
Controller:
if #program.save
redirect_to faculty_program_path(faculty, #program)
else
render :new,:json => #program.errors
end
What I get from RSpec
Failure/Error: expect(response).to render_template(:new)
expecting <"new"> but rendering with <[]>
This means that the record has been saved and you have been redirected. If you want to check behaviour when invalid data is passed, do not rely on existing validations as they might change in the future. Just stub valid? method:
it "case of invalid entry" do
program=FactoryGirl.attributes_for(:program)
allow_any_instance_of(Program).to receive(:valid?).and_return false
post :create, {:program=>program, :faculty_shortcut=>faculty }
expect(response).to render_template(:new)
end
Stubbing valid? has this advantage that your test will not change when you change your validations. Validations on their own should be tested within model tests, this way single change in the code won't cause multiple test fails.

Rspec stub/mock with authentication token (authlogic)

first posting on this helpful community, but have been using it since the start of my programming journey. I'm new to rails and have trouble writing tests. So far, I've been trying to write a controller test based on the Authlogic perishable token guide .
But during the test, it keeps getting stuck on the line
#controller
#user = User.find_using_perishable_token(params[:activation_code], 1.week) || (redirect_to new_user_session_url and return)
and here's the line in the test that gets into trouble:
#test
User.any_instance.stub(:find_using_perishable_token) {mock_model(User, stubs(:active => true)) }
get :create, :activation_code => 'valid'
response.should render_template 'new'
response.code.should == "200"
#the test response is 302, not 200
response.should redirect_to("/")
#the test shows the path as user_sessions/new,
#meaning it didn't pass the condition in the controller with the find_using_pt method
Heres the test output:
Failure/Error: response.should render_template 'new' expecting <"new"> but rendering with <"">
# ./spec/controllers/activations_controller_spec.rb:18:in `block (3 levels) in <top (required)>'
After reading one of the posted answers ,it seems that I'm not creating the stub/mock correctly and that's why it keeps getting the redirect_to (302) rather than continuing with the test. (the find_using_pt is a method from Authlogic). Hence, the trouble is with the redirect_to line as it never continues with the controller/page render
Any advice would be greatly appreciated. Thanks.

Rails 3.1 Rspec Rails 2 - How can I test this controller action?

I need to know how to test this controller action
def create_mobile
if mobile_user = MobileUser.authenticate(params[:email], params[:password])
session[:mobile_user_id] = mobile_user.id
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :unauthorised }
end
end
end
The route is a post request to sessions/create_mobile and as you can see the action only responds to json
My current controller spec looks like this
describe SessionsController, "Logging in" do
before(:each) do
#mobile_user = FactoryGirl.create(:valid_mobile_user)
end
it "Should log in mobile user" do
#request.env["HTTP_ACCEPT"] = "application/json"
post :create_mobile, {:password => #mobile_user.password, :email => #mobile_user.email}
response.should be_success
end
it "should fail log in mobile user" do
#request.env["HTTP_ACCEPT"] = "application/json"
post :create_mobile, {:password => 'joe', :email => #mobile_user.email}
response.should_not be_success
end
end
The test results in
1) SessionsController Logging in should log in mobile user
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/sessions_controller_spec.rb:11:in `block (2 levels) in <top (required)>'
So there is either a problem with my application code or there is a problem with my test code but either way there is no problem with the authenticate method as the MobileUser model spec passes which looks like this
it "should authenticate" do
mu = FactoryGirl.create(:valid_mobile_user)
assert_equal 1, MobileUser.count
assert_equal mu, MobileUser.authenticate(mu.email, mu.password)
end
Any help in sorting this out greatly appreciated.
Thanks in advance
UPDATE
As suggested below, using
post :create_mobile, {:password => 'joe', :email => #mobile_user.email} :format => :json
or using
#request.env["HTTP_ACCEPT"] = "application/json"
or a combination of both makes no difference
UPDATE 2
The test has just started working for no reason that I can fathom (other than I never understood why it wasn't working in the first place).
Totally strange!
I met the same issue recently. below is my test code
post :create, :login => 'mock_user', :password => 'passwd', :format => :json
expected = {
:login => 'mock_user'
}.to_json
session[:user_id].should == mock_user.id
response.body.should == expected
The test result is false, caused by response.body is blank. I checked the test.log, and found got 406 unacceptable request error in controller. After googled a lot, I changed to :format => 'json'. Then I got the expected test result.
Since you're trying to post JSON to the controller, shouldn't you convert your hash to JSON?
post :create_mobile, {:password => 'joe', :email => #mobile_user.email}.to_json
If you don't want to add seven characters to your code just to see if it works, then you should output your params to logger to see what they look like.
def create_mobile
logger.info "LOGIN PARAMS U/N: #{params[:email]}, P/W: #{params[:password]}"
...
Then tail -f log/test.log to see what your controller looks like during your test.
have you tried adding a :format option to the post statement

Rspec testing redirect_to :back

How do you test redirect_to :back in rspec?
I get
ActionController::RedirectBackError:
No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].
How do I go about setting the HTTP_REFERER in my test?
Using RSpec, you can set the referer in a before block. When I tried to set the referer directly in the test, it didn't seem to work no matter where I put it, but the before block does the trick.
describe BackController < ApplicationController do
before(:each) do
request.env["HTTP_REFERER"] = "where_i_came_from"
end
describe "GET /goback" do
it "redirects back to the referring page" do
get 'goback'
response.should redirect_to "where_i_came_from"
end
end
end
From the rails guide when requesting the request with the new request style:
describe BackController < ApplicationController do
describe "GET /goback" do
it "redirects back to the referring page" do
get :show,
params: { id: 12 },
headers: { "HTTP_REFERER" => "http://example.com/home" }
expect(response).to redirect_to("http://example.com/home")
end
end
end
If someone stumbles upon this and they're using request specs, you'll need to explicitly set the headers on the request you're making. The format of the test request depends on which version of RSpec you're using and if you can use keyword arguments instead of positional arguments.
let(:headers){ { "HTTP_REFERER" => "/widgets" } }
it "redirects back to widgets" do
post "/widgets", params: {}, headers: headers # keyword (better)
post "/widgets", {}, headers # positional
expect(response).to redirect_to(widgets_path)
end
https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec
IMHO the accepted answer is a bit hacky. A better alternative would be to set the HTTP_REFERER to an actual url in your application and then expect to be redirected back:
describe BackController, type: :controller do
before(:each) do
request.env['HTTP_REFERER'] = root_url
end
it 'redirects back' do
get :whatever
response.should redirect_to :back
end
end
Redirecting to a random string constant feels as if it works by accident
You take advantage of rspec's built in functionality to express exactly what you wanted
You don't introduce and repeat magic string values
For newer versions of rspec, you can use expectations instead:
expect(response).to redirect_to :back
In regard to testing :back links in integration tests, I first visit a deadend page, that I think is not likely to be ever used as a link, and then the page I am testing. So my code looks like this
before(:each) do
visit deadend_path
visit testpage_path
end
it "testpage Page should have a Back button going :back" do
response.should have_selector("a",:href => deadend_path,
:content => "Back")
end
However this does have the flaw that if the link is really to the deadend_path, then the test will incorrectly pass.
request.env['HTTP_REFERER'] = '/your_referring_url'

Resources