Unit testing rails controller, class not receiving method... Why? - ruby-on-rails

Say I have a users controller with a separate CreateUser class that when called creates a user. I have CreateUser in my controller and want to make sure that it is being called on.
I'm getting an ActiveRecord error. Very confused because I'm stubbing out the CreateUser class so I'm not touching the database. Confused why CreateUser is not being called on.
What am I doing wrong here?
Controller spec:
# spec/controllers/users_controller_spec.rb
# ...
describe "create action" do
before do
# build fake data
created_user = FactoryGirl.build_stubbed(:user)
result = double(:context, user: created_user, success?: true)
# don't hit the database by stubbing creation process
allow(CreateUser).to receive(:call).and_return(result)
end
after do
post :create, user: { name: "bob" }
end
# this test fails
it "calls on CreateUser interactor" do
expect(CreateUser).to receive(:call)
end
# this test passes
it "renders something in json" do
expect(controller).to receive(:render)
end
end
Controller:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
# ...
def create
result = CreateUser.call # why is this not being called?
render json: result.user, status: :created
end
private
def user_params
params.permit(:name)
end
end
Output:
Failure/Error: expect(CreateUser).to receive(:call)
(CreateUser (class)).call(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments

The issue I was having was because of CanCan's load_and_authorize_resource. The fix is to stub out that method at top of RSpec test:
allow_any_instance_of(CanCan::ControllerResource).to receive(:load_resource)

Related

Rspec - Stub/allow_any_instance_of included module methods is not working

I've been trying to stub a private module method for the whole day now but with not progress.
Here is a snippet of my application controller class
class ApplicationController < ActionController::Base
include Cesid::Application
end
Cesid > Application.rb
module Cesid
module Application
extend ActiveSupport::Concern
included do
before_action :track_marketing_suite_cesid, only: [:new]
end
private
def track_marketing_suite_cesid
return unless id_token_available?
## #cesid_auth = Auth.new(#id_token)
#cesid_auth = Auth.new(id_token)
return unless #cesid_auth.present? && #cesid_auth.valid?
#cesid_admin = Admin.where(email: #cesid_auth.email).first_or_initialize
end
def id_token_available?
## #id_token.present?
id_token.present?
end
def id_token
#id_token ||= id_token_param
end
def id_token_param
cookies[:id_token]
end
end
end
Now, I'm trying to create a simple unit test for the method
id_token_available?
And I am just trying to set the id_token_param to a random value.
I've tried using this code as stated Is there a way to stub a method of an included module with Rspec?
allow_any_instance_of(Cesid).to receive(:id_token_param).and_return('hello')
but I just get this error
NoMethodError:
undefined method `allow_any_instance_of' for #<RSpec::ExampleGroups::CesidApplication::CesidAuthorizations::GetCesidApplication:0x00007fa3d200c1c0> Did you mean? allow_mass_assignment_of
Rspec file
require 'rails_helper'
describe Cesid::Application, :type => :controller do
describe 'cesid application' do
before do
allow_any_instance_of(ApplicationController).to receive(:id_token_param).and_return('hello')
end
it 'returns true if the id_token is present' do
expect(Cesid::Application.send('id_token_available?')).to eql(true)
end
end
end
Rspec version
3.5.4
This is honestly starting to drive me crazy
I see three issues:
You call allow_any_instance_of in a context in which it is not defined. allow_any_instance_of can be used in before blocks. I need to see your RSpec code to be more specific.
Actually your code is called on the ApplicationController, not on the module, therefore you need to change your stub to
allow_any_instance_of(ApplicationController).to receive(:id_token_param).and_return('hello')
Currently id_token_param will not be called at all, because id_token_available? checks the instance variable and not the return value of the id_token method that calls the id_token_param. Just change the id_token_available? to:
def id_token_available?
id_token.present?
end
There's a much better way of going about this test. The type: :controller metadata on your spec gives you an anonymous controller instance to work with.
Here's an example of how you could write this to actually test that the before_action from your module is used:
describe Cesid::Application, type: :controller do
controller(ApplicationController) do
def new
render plain: 'Hello'
end
end
describe 'cesid before_action' do
before(:each) do
routes.draw { get 'new' => 'anonymous#new' }
cookies[:id_token] = id_token
allow(Auth).to receive(:new).with(id_token)
.and_return(instance_double(Auth, valid?: false))
get :new
end
context 'when id token is available' do
let(:id_token) { 'hello' }
it 'sets #cesid_auth' do
expect(assigns(:cesid_auth)).to be_present
end
end
context 'when id token is unavailable' do
let(:id_token) { '' }
it 'does not set #cesid_auth' do
expect(assigns(:cesid_auth)).to be_nil
end
end
end
end

Reset Password testing with Rspec

i am using rails and want to write a test for password reset in Rspec. i am quite new to testing.
this is what i have done so far:
require 'rails_helper'
describe UsersController, type: :controller do
describe 'post #reset_password' do
let(:user) { create(:user) }
context "reset password" do
def do_request
patch :update_password
end
before { do_request }
it { expect(ActionMailer::Base.deliveries.count(1) }
end
end
end
every time i run this it gives ma an syntax error in
"it { expect(ActionMailer::Base.deliveries.count(1) } ".
i want to check whether the email successfully sent of not and if the user have key in the email.
Thanks!
1) you miss ) at last here so got syntax error
it { expect(ActionMailer::Base.deliveries.count(1) }
to
it { expect(ActionMailer::Base.deliveries.count(1)) }
2)
If you want to check total deliveries. you can try
it 'should send an email' do
ActionMailer::Base.deliveries.count.should == 1
end
also check sender
it 'renders the sender email' do
ActionMailer::Base.deliveries.first.from.should == ['notifications#domain.com']
end
Also check subject line
it 'should set the subject to the correct subject' do
ActionMailer::Base.deliveries.first.subject.should == 'Here Is Your Story!'
end
The problems you're having will most likely be fixed by writing better tests.
Here's generally how you would write tests for something like this.
Lets suppose in your routes file you have a post route that looks something like this
# config/routes.rb
post "/user/:id/reset_password", to: "users#reset_password"
And your User controller looks something like this
# app/controllers/users_controller.rb
class UsersController
...
def reset_password
user = User.find(params[:id])
user.reset_password!
SomeMailClass.email_reset_instructions(user)
end
end
and your User.rb model looks something like this
# app/models/user.rb
class User < ActiveRecord::Base
def reset_password!
update!(password: nil) # or whatever way you want/need to reset the password
end
end
and you have some type of mailing class to send your email
# app/models/some_mail_class.rb
class SomeMailClass
def self.email_reset_instructions(user)
# do something to send email...
end
end
The way you would go about testing this in the controller would be
# spec/controllers/users_controller_spec.rb
require 'rails_helper'
describe UsersController, type: :controller do
it "#reset_password" do
user_id = double(:user_id)
user = double(:user)
expect(User).to receive(:find).with(user_id).and_return(user)
expect(user).to receive(:reset_password!).and_return(true)
expect(SomeMailClass).to receive(:email_reset_instructions).with(user)
post :reset_password, id: user_id
end
end
But you shouldn't stop there. Because the implementation of the newly made method reset_password! and the SomeMailClass has yet to be tested. So you would write model/unit tests like this for them
# spec/models/user_spec.rb
require "rails_helper"
describe User do
it ".reset_password!" do
user = User.create(password: "foo")
expect(user.password).to eq "foo"
user.reset_password!
expect(user.password).to eq nil
end
end
Then you might install vcr and factory_girl gems and use them like so to test your mailer
# spec/models/some_mail_class_spec.rb
require "rails_helper"
describe SomeMailClass do
VCR.use_cassette "email_reset_instructions" do |cassette|
it ".email_reset_instructions" do
user = FactoryGirl.create(:user)
SomeMailClass.email_reset_instructions(user)
# you can write some expectations on the cassette obj to test.
# or you can write whatever expectations you need/desire
end
end
end
And in the end if there was something happening on the front end that a user would click that made this post request you would write a feature test for it as well.
Hope this helps!

Controller testing Rspec, Rails 4.2

I am just starting with RSpec and I am a bit lost. Can anyone please tell me how to write controller test for this method?
def list_c
#c = params.has_key?(:car) ? Car.new(car_listing_params) : Car.new()
#car.user = #user
#car.number_of_beds = 4 unless #car.number_of_beds
#car.car_type_tag = 'car' unless #car.car_type
#page_title = t('home.list_your_car')
end
A small example of controller test with rspec. You may get some idea from here.
# define your helper here.
require 'rails_helper'
# respect method to define test for your controller.
RSpec.describe YourController, type: :controller do
#define your factories for the set of your test
# you will decide which one you need to create from factory. Factory girl
# is used to mock factories.
# #c = params.has_key?(:car) ? Car.new(car_listing_params) : Car.new()
# #car.user = #user
# #car.number_of_beds = 4 unless #car.number_of_beds
# #car.car_type_tag = 'car' unless #car.car_type
# #page_title = t('home.list_your_car')
let(:user) { FactoryGirl.create :user }
before { sign_in user }
describe '#title' do
context 'with valid params' do
before do
# in before block, you can request a http call
# with parameter. This action repeats before each test runs.
get :your_action_of_current_controller, {a_param: a_value, a_date: "16-4-2015"}
end
it "renders successfully" do
# when you call a get request it assigns a response object.
expect(response).to be_success
end
it "returns json" do
expect(response.content_type).to be == 'application/json'
end
end
end
end

Rspec controller test for callback after_save

I am trying to test to see if posting to a create method in my controller triggers a callback I defined with after_save
Here's the controller method being posted to
def create
#guest = Guest.new(guest_params)
#hotel = Hotel.find(visit_params[:hotel_id])
#set visit local times to UTC
#visit= Visit.new(visit_params)
#visit.checked_out_at = (DateTime.now.utc + visit_params[:checked_out_at].to_i.to_i.days).change(hour: #visit.hotel.checkout_time.hour)
#visit.checked_in_at = Time.now.utc
##visit.user_id = current_user.id
#self_serve = (params[:self_serve] && params[:self_serve] == "true")
if #guest.save
#visit.guest_id = #guest.id
if #visit.save
if #self_serve
flash[:notice] = "#{#visit.guest.name}, you have successfully checked in!."
redirect_to guest_checkin_hotel_path(#visit.hotel)
else
flash[:notice] = "You have successfully checked in #{#visit.guest.name}."
redirect_to hotel_path(#visit.hotel)
end
else
render "new"
end
else
render "new"
end
end
Here's my spec/controllers/guests_controller_spec.rb test that is failing
RSpec.describe GuestsController, :type => :controller do
describe "#create" do
let!(:params) do { name: "John Smith", mobile_number: "9095551234" } end
context "when new guest is saved" do
it "triggers create_check_in_messages callback" do
post :create, params
expect(response).to receive(:create_check_in_messages)
end
end
end
end
Here is my models/concerns/visit_message.rb callback file
module VisitMessage
extend ActiveSupport::Concern
included do
after_save :create_check_in_messages
end
def create_check_in_messages
. . .
end
end
Here is the fail message when I run 'rspec spec/controllers/guests_controller_spec.rb'
1) GuestsController#create when new guest is saved triggers create_check_in_messages callback
Failure/Error: post :create, params
ActionController::ParameterMissing:
param is missing or the value is empty: guest
# ./app/controllers/guests_controller.rb:63:in `guest_params'
# ./app/controllers/guests_controller.rb:10:in `create'
# ./spec/controllers/guests_controller_spec.rb:36:in `block (4 levels) in <top (required)>'
I've been searching all over stackoverflow with no luck. I appreciate any help!
I am assuming that the guest_params method in the controller looks something like this:
def guest_params
params.require(:guest).permit(....)
end
If that is the case, you need to update the POST call in your test case thusly:
post :create, {guest: params}
On a side note, your controller is unnecessarily bloated. I would read up on working with associated models to streamline your code, specifically, using accepts_nested_attributes_for:
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Using Shoulda to test for an object expecting a method

In the following example the #user.expects(:send_invitations!).once assertion is failing, even though the invitations are being sent by the app and the #send_invitations variable is being assigned. Would you expect #user.send_invitations! to be invoked at this point or is #user.expects(:send_invitations!) being used incorrectly?
The Controller
class RegistrationsController < ApplicationController
before_filter :require_active_user
def welcome
if params[:send_invitations]
current_user.send_invitations!
#send_invitations = true
end
end
end
The Controller Test
require 'test_helper'
class RegistrationsControllerTest < ActionController::TestCase
context "With a logged-in user" do
setup { login_as #user = Factory(:user) }
context ":welcome" do
context "with params[:send_invitations] present" do
setup { get :welcome, { :send_invitations => true } }
should "send invitations" do
#user.expects(:send_invitations!).once # tests say this isn't being invoked
assert assigns(:send_invitations) # but tests say this IS being assigned
end
end
context "without the presence of params[:send_invitations]" do
setup { get :welcome }
should "not send invitations" do
#user.expects(:send_invitations!).never # passes fine
assert !assigns(:send_invitations!) # also passes fine
end
end
end
end
end

Resources