RSpec how to mock method inside another method - ruby-on-rails

In application_controller I've got two methods which results I want to test in a maintenance_mode_controller_specs. How to create mock maintenance_mode_active? which will return false to use it inside check_maintenance??
application_controller.rb
before_action :check_maintenance?
private
def check_maintenance?
if maintenance_mode_active? == true
redirect_to maintenance_mode
elsif request.fullpath.include?(maintenance_mode_path)
redirect_to :root
end
end
def maintenance_mode_active?
# do sth ...
mode.active?
end
maintenance_mode_controller_spec.rb
context 'when maintenance mode is active' do
let(:maintenance_mode?) { instance_double(ApplicationController) }
before do
allow(ApplicationController).to receive(:maintenance_mode_active?).and_return(false)
end
it 'redirect to root path' do
expect(described_class).should redirect_to(maintenance_mode_path)
end
end

maintenance_mode_active is an instance method and you stub it on the class level. You need to use allow_any_instance_of
before do
allow_any_instance_of(ApplicationController).to receive(:maintenance_mode_active?).and_return(false)
end

Related

Rspec - Argument error after overwriting method_missing and respond_to_missing

I have a controller that i want to write rspec for
results_controller.rb
class Api::V1::ResultsController < Api::V1::ApplicationController
before_action :devices
include DataHelper
def show
results = get_dr_results
render json: { data: results }
end
private
def get_dr_results
program_ids = method_defined_in_crucible_helper
end
end
module DataHelper
include Cruciblehelper
def method_missing(method_name, *args, &block)
if condition
do_something
else
super.method_missing(method_name, *args, &block)
end
end
def respond_to_missing?
true
end
end
module CrucibleHelper
def method_defined_in_crucible_helper
end
end
Now in my rspec, I try to mock the method method_defined_in_crucible_helper.
describe Api::V1::DrResultsController, type: :controller do
describe 'GET #show' do
before do
allow_any_instance_of(CrucibleHelper).to receive(:method_defined_in_crucible_helper) { [utility_program.id, utility_program2.id] }
end
context 'returns data' do
context 'returns expected events' do
it 'should return success response with expected events' do
get :show
expect(JSON.parse(response.body)).to eq(expected_response)
end
end
I am getting
Failure/Error:
def respond_to_missing?
true
end
ArgumentError:
wrong number of arguments (given 2, expected 0)
# ./app/helpers/data_helper.rb:72:in `respond_to_missing?'
If I comment out respond_to_missing? method, then my specs are executing OK. Can someone help me in fixing this error?
Ruby Delegator#respond_to_missing? is method take responsible for returning whether a missing method be able to handled by the object or not, it takes 2 parameters: the missing method name and the option include_private.
The best practice is: always define respond_to_missing? when overriding method_missing.
However i do not prefer the way you applied, the reason behind that is The Rule of Least Surprise, take a look:
class DataHelper
def method_missing(method_name, *args, &block)
if method_name.to_s.start_with?('delegate')
puts "a delegate method"
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
true
end
end
d = DataHelper.new
d.respond_to?(:answer) # true
d.answer # `method_missing': undefined method `answer' ... SURPRISE
as you can see, d response that he can responsible for the answer method but when call that method, a method_missing error be raised.
So, you need to make both method_missing and respond_to_missing? match together:
class DataHelper
def method_missing(method_name, *args, &block)
if can_handle?(method_name)
puts "a delegate method"
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
return true if can_handle?(method_name)
super
end
private
def can_handle?(method_name)
method_name.to_s.start_with?('delegate')
end
end
d = D.new
d.respond_to?(:delegate_answer) # true
d.delegate_answer # delegate method
d.respond_to?(:answer) # false
d.answer # error

Dynamically add method in before(:all)

Im testing a Module that can be included in a Controller.
Right now the code looks like that:
class GithubAuthorizationController < ApplicationController
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
describe GithubAuthorizationController do
before(:all) do
#page_content = "lorem_ipsum"
end
...........
As you can see I basically create a Test-Controller before the tests are run. Now I would like to add the module and index method in the before(:all)-block. I tried:
class GithubAuthorizationController < ApplicationController
end
describe GithubAuthorizationController do
before(:all) do
#page_content = "lorem_ipsum"
class < #controller
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
end
...........
As I can see in debugger in the before(:all) block the #controller is defined as <GithubAuthorizationController .... So It is a instance. There Is also no error when running the code, but the tests fails, because of The action 'index' could not be found ...
What do I wrong? How can I move the code to the before(:all) block? Thanks
The way to do this in rspec is with a controller block:
describe GithubAuthorizationController, type: :controller do
context "with module" do
controller do
include Frontend::Concerns::GithubAuthorization
def index
render inline: "lorem_ipsum"
end
end
# within this block the controller will be the anonymous controller class created above
end
end
If you have set infer_base_class_for_anonymous_controllers to false (this is not the default) then you need to do controller(GithubAuthorizationController) or you'll inherit directly from ApplicationController
Your issue could be down to a missing route - the controller helper creates some routes for you (for the default index, show etc.) actions. You can add extra ones in an example with
it "does something with a custom route" do
routes.draw { get "custom" => "github_authorization#custom" }
get :custom
...
end

Test private method of controller with rspec

I have
class Api::V1::BaseController < ApplicationController
before_filter :authenticate!
private
def authenticate!
session_token = request.env["HTTP_SESSIONTOKEN"]
#current_user = User.where(session_token: session_token).first unless session_token.blank?
unless #current_user
return render_json({message: "ERROR: Invalid Session"})
end
end
def current_user
#current_user
end
end
I am testing session_controller which inherits base_controller
before do
post :create, {email: "raj#gmail.com", user_name: "raj",password: "raj"}
body = JSON.parse(response.body)
#session_token = body["session_token"]
end
describe "PUT #edit_email" do
context "if new email already exist" do
it "should return with a error message" do
put :edit_email, {email: "raj#gmail.com", new_email: "ravi#gmail.com", password: "raj"}
body = JSON.parse(response.body)
expect(body["message"]).to eq("email already exist")
end
end
end
I am new to rspec and here I am confused about calling private authenticate method with session_token.
How to call private method of controller and pass header.Thanks in advance.
In Ruby/Rails and OOP in general private methods should not be tested. You should test the interface, not the implementation of private method.
Implementation could be changed with time - but the interface it provides would (most likely) not.
You can set your env variable like following:
request.env['HTTP_SESSIONTOKEN'] = "..."
For testing your private method:
controller.instance_eval{ authenticate }.should eql ...

What's the right way to test rails controllers in isolation?

Even a seemingly simple index action feels incredibly complicated to test in isolation.
I find myself having to mock out several of my User and Tenant methods just to get through the before_filters. Then I need to mock out Kaminari and Tenant#users for the action.
This feels excessive for testing a controller action with no control flow.
TDD principle would say that an excessive need for mocking is a sign of poor design, but then I'm not sure how I would extract this functionality into a domain object.
Is this sort of painful mocking standard for testing Rails controllers? Is there better way to do this that I'm simply not aware of?
For instance, perhaps skipping before_filters would make this less painful, but as they are consequential private methods, I feel that skipping them is missing the point.
class UsersController < AdminController
before_filter :check_auth
before_filter :check_admin
around_filter :set_tenant_time_zone, if: current_tenant
def index
Kaminari.paginate(current_tenant.users).page(params[:page])
end
private
def current_user
# gets user from session
end
def current_tenant
current_user.tenant if current_user
end
def set_tenant_time_zone
Time.use_zone(current_tenant.time_zone, &block)
end
def check_auth
redirect_to login_url unless AuthChecker.new(current_user, request.remote_ip).has_access?
end
def check_admin
redirect_to root_url unless current_user.is_admin?
end
end
You have to do all those mocks/stubs if you want to run those before_filters but I think, that, for those cases, is better to use some spec helper method to create a logged in user so, on your spec, you only need to call that method on a "before(:each)" block of your controller where you want a user.
In spec_helper.rb:
def current_user(stubs = {})
unless #current_user
u = FactoryGirl.build(:user, stubs)
u.save(:validate => false)
#current_user = u
end
#current_user
end
def current_user_session(stubs = {}, user_stubs = {})
#current_session ||= mock_model("Session", {:record => nil, :user => current_user(user_stubs)}.merge(stubs))
end
def login(session_stubs = {}, user_stubs = {})
UserSession.stub(:find).and_return(current_user_session(session_stubs, user_stubs))
controller.stub(:current_user => #current_user)
end
so, on the controller specs that require a logged in user with some special stub I can do
describe 'GET index' do
before(:each) do
login #this does all you need to pass the filters
end
it 'does something' do
current_user.stub(:some_method)
get :index
expect(response).to something
end
end
that way the test only has stubs, instances and expectations for the actual code of the action and not the filters

Stubbing Grape helper

I have Rails app with Grape API.
The interface is done with Backbone and Grape API provides it all data.
All it returns is user-specific stuff, so i need reference to currently logged in user.
Simplified version looks like this:
API initialization:
module MyAPI
class API < Grape::API
format :json
helpers MyAPI::APIHelpers
mount MyAPI::Endpoints::Notes
end
end
Endpoint:
module MyAPI
module Endpoints
class Notes < Grape::API
before do
authenticate!
end
# (...) Api methods
end
end
end
API helper:
module MyAPI::APIHelpers
# #return [User]
def current_user
env['warden'].user
end
def authenticate!
unless current_user
error!('401 Unauthorized', 401)
end
end
end
So, as you can see, i get the current user from Warden and it works fine. But the problem is with testing.
describe MyAPI::Endpoints::Notes do
describe 'GET /notes' do
it 'it renders all notes when no keyword is given' do
Note.expects(:all).returns(#notes)
get '/notes'
it_presents(#notes)
end
end
end
How can I stub helpers's method *current_user* with some specific user?
I tried:
setting env/request, but it doesn't exist before calling get method.
stubbing MyAPI::APIHelpers#current_user method with Mocha
stubbing MyAPI::Endpoints::Notes.any_instance.stub with Mocha
Edit:
At the moment, it's stubbed this way:
spec:
# (...)
before :all do
load 'patches/api_helpers'
#user = STUBBED_USER
end
# (...)
spec/patches/api_helpers.rb:
STUBBED_USER = FactoryGirl.create(:user)
module MyAPI::APIHelpers
def current_user
STUBBED_USER
end
end
But it's definitely not the answer :).
comments mentioned in this issue should help you, It's how even Grape tests it's helpers,
https://github.com/intridea/grape/blob/master/spec/grape/endpoint_spec.rb#L475
(If the code is not there on the same line due to changes, just do a ctrl+f & look for helpers)
Here's some code from the same file
it 'resets all instance variables (except block) between calls' do
subject.helpers do
def memoized
#memoized ||= params[:howdy]
end
end
subject.get('/hello') do
memoized
end
get '/hello?howdy=hey'
last_response.body.should == 'hey'
get '/hello?howdy=yo'
last_response.body.should == 'yo'
end
Option 1
The recommended way is to use Grape::Endpoint.before_each:
context 'when user is logged in' do
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:current_user).and_return(user)
end
end
after { Grape::Endpoint.before_each nil }
end
But this is quite verbose. It can live in a shared context, but you can't pass user as a parameter explicitly so you'd end up with:
let(:user) { create(:user) }
# ...
include_context 'signed in user'
Option 2
My preferred way is a more RSpec-like stubbing:
# helper
module AuthHelper
def current_user
# ...
end
end
# api
module API
module V1
class Auth < Grape::API
helpers AuthHelper
end
end
end
# spec
before do
allow_any_instance_of(AuthHelper).to receive(:current_user).and_return(user)
end
Option 3
You can also define helpers:
API::V1::User.helpers do
def current_user
user
end
end

Resources