I'm writing a controller test, and I wanted to verify Controller1's call_dashboard_function.
class Controller1 < ApplicationController
before_action :init_dashb
def call_dashboard_function
#dashb.do_something!
end
def init_dashb
#dashb ||= Dashboard.new(current_user)
end
end
class Dashboard
#... this is a facade class
end
Actual test:
test "should do something" do
sign_in #user
patch :call_dashboard_function
end
The problem is I don't want to test the dashboard class, since I have a separated test for it. So I'd like to mock, stub, etc this class behavior
That would be easy if I had access from outside. But I can't see dashboard class from outside the controller.
you should stub it:
let(user) { double('user') } # if you don't need real user
# or
let(user) { create(:user) } # if you need real user
before { allow_any_instance_of(Dashboard).to receive(:current_user) { user } }
Related
Attempting to write Rspec tests for a controller which accepts api requests from another server/service.
The code below accepts a request launched from another service for an authenticated user in the form /servers/users/some/create (post request)
module Servers
module Users
class SomeController < ::Servers::Users::BaseController
def create
some_service.create!
render json: {status: 'ok'}
end
end
def some_service
if current_user.randommodel.nil?
#some_service ||= ::SomeService.new(current_user)
end
randommodel is embedded within the user class and by default does not exist. Some_Service is responsible for creating this randommodel
class SetupMfaService
attr_accessor :current_user
def initialize(current_user)
#current_user = current_user
end
def create!
current_user.randommodel = randommodel.new
current_user.randommodel.save!
end
This service class has been fully covered with tests and works as expected
SomeService has been fully covered with tests and works completely fine. (It works). However when I attempt to test this method through doing:
require 'rails_helper'
require 'requests/servers/servers_api_context'
RSpec.describe '::Servers::Users::SomeController' do
include_context "Servers API"
let!(:user) { Fabricate(:user, { clear_password: password }) }
let(:request_auth_data) do
{
user_id: user.id,
account_id: user.account_id,
role: ''
}
end
let(:source_server) { 'web_application' }
let(:endpoint) { 'localhost' }
context 'create' do
subject do
servers_api_post '/servers/users/some/create'
end
it 'returns a 200' do
response = subject
expect(response.status).to be(200)
end
it 'returns the expected JSON data' do
response = subject
expect(response.data).to eql({})
end
end
end
Tests are failing with no method error 'somemodel' for nil:NilClass which means to me it is not able to detect the presence of current_user (Which is available in the base controller which my controller inherits from as attr_reader :current_user. There also exists a decoded_payload object which allows us to read the parameters present in the requests handled which contains the user id which I've tried to do a look up for in the controller below despite the fact this process is done in the BaseController that this controller inherits from too. Which leads me to the conclusion the way in which I am writing the test is wrong. How can I explicit the current user parameter in the rspec test for this controller file? ~
Logging in for requests is handled in BaseController where it is inherited down into my SomeController
def authenticate
#jwt = case request_source
when 'web_application'
::Servers::WebApplication::JwtAuthService.new(endpoint).decrypt_and_verify(jwe)
else
raise 'Unknown request source'
end
#current_user = User.find(decoded_payload[:user_id])
#current_account = current_user&.account
raise ::Api::Error::AccountNotFound.new if current_account.blank?
end
Assuming that by somemodel you mean current_user.randommodel.nil?, then your tests are not logged in. The tests declare request_auth_data but don't appear to do anything with it.
I can't tell you how to login, just that your tests must do it.
Also your tests reveal a bug in your controller. If a login is required the API should have responded with 401 Unauthorized, not an exception. Often this is handled by inheriting from a controller base class which requires and handles authorization.
My controllers inherit actions from ApplicationController. My goal is to test the behaviour of any controller that inherits from ApplicationController. I created RandomController in my specs in order to achieve that goal.
Here is my spec so far
require 'rails_helper'
RSpec.configure do |c|
c.infer_base_class_for_anonymous_controllers = false
end
class RandomController < ApplicationController; end
class Random < ApplicationRecord; end
RSpec.describe RandomController, type: :controller do
controller {}
describe '.index' do
context 'when no record exists' do
before { get :index }
specify { should respond_with(200) }
end
end
end
Here is application_controller
class ApplicationController
def index
binding.pry
end
end
The issue is that when the index method runs, self.class returns #<Class:0x00007f8c33b56fc8> instead of RandomController. Is it possible to have my anonymous controller be an instance of a given controller (declared within the specs) ?
According to the docs you can specify the base class for the anonymous controller:
To specify a different base class you can pass the class explicitly to the
controller method:
controller(BaseController)
https://relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller
Thus you can probably call:
controller(RandomController)
in your specs
Consider using shared_context instead of creating a RandomController to test shared code:
shared_context 'an application controller' do
describe '#index' do
context 'when no record exists' do
before { get :index }
expect(response).to have_http_status(:ok)
end
end
end
You would typically put this file under /spec/support. Example:
/spec/support/shared_contexts_for_application_controllers.rb
Then, in each controller that inherits from ApplicationController:
describe RandomController do
include_context 'an application controller'
end
I'm new to Rspec and I am trying to get into the whole BDD mindset, so I'm pretty stumped about this error. I have have rails engine that I am trying to test. Here is the bulletin controller. Basically before any action I want to populate the list of courses.
class BulletinsController < ApplicationController
before_filter :get_courses
def new
#bulletin = Bulletin.new(author_id: #user.id)
end
...
private
def get_courses
if #user.has_role? :admin
#course_list = Course.all.sort_by(&:start_date)
...
end
end
The application controller has some methods that I want run on each request. I am using devise in the host app so I have access to the current_user method
class ApplicationController < ::ApplicationController
before_filter :get_user
...
def get_user
#user = current_user
end
...
end
And here is the spec I am trying to run:
describe BulletinsController do
routes { MyEngine::Engine.routes }
before { controller.stub(:authenticate_user!).and_return true }
before { controller.stub(:get_user).and_return (#user = create(:user)) }
describe "GET #new" do
it "assigns a new bulletin to #bulletin" do
bulletin = create(:bulletin)
controller.stub(:get_courses)
get :new
assigns(:bulletin).should eq(bulletin)
end
end
end
When I try to run the spec, I get the error:
NoMethodError: undefined method 'id' for nil:NilClass
I understand that I am getting this because #user is not defined when it is called in the bulletin building; however I thought that the before block in the spec would define the #user variable after stubbing out the :get_user filter. When I test the factories in the console, everything seems to be created with the proper associations (bulletin -> author, bulletin -> course, etc).
I'm not sure what I'm missing as to why the #user variable is not being carried through to my controller code. Any insight and/or good tutorials for rspec would be greatly appreciated.
Trying to stub out the methods that Devise could be using will be quite difficult unless you understand how Devise works.
The recommend way to test is to simply sign in the user using Devise test helper as per their documentation:
https://github.com/plataformatec/devise#test-helpers
describe BulletinsController do
routes { MyEngine::Engine.routes }
before { sign_in(user) }
let!(:user) { create(:user) }
describe "GET #new" do
it "assigns a new bulletin to #bulletin" do
bulletin = create(:bulletin)
controller.stub(:get_courses)
get :new
assigns(:bulletin).should eq(bulletin)
end
end
end
This way, you won't have to care about Devise methods and stubbing it. Just focus on testing your own method. :)
I guess You also need to stub current_user and it will be enough (no need to stub get_user):
before { controller.stub(:current_user).and_return (#user = create(:user)) }
And i guess the good practice is to let user (if you need it more than once):
routes { MyEngine::Engine.routes }
let!(:user) { create(:user) }
before { controller.stub(:current_user).and_return user }
If you need an access to private methods, you can try something like this:
subject.send(:current_user=, user)
Could be a controller instead of subject, not sure what version which supports.
Update. Actually, it's really tricky to test private methods. I checked that current_user in devise defines like:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
So, you can try stub warden.authenticate to returns user:
allow_any_instance_of(Warden).to receive(:authenticate).and_return(create(:user))
I've set up a controller I'm testing so that most requests to its actions redirect to the sign_in page if a session doesn't exist.
Because of this, I need to use the sign_in method when testing controllers in a unit test. I also need to create the user in the database so they can sign in.
This is all easily achieved like this:
describe MyController, do
let(:user) { FactoryGirl.create(:user)} # The 'create' creates in the database as well as in memory
context "with session" do
before {
sign_in user
}
context ".index" do
assigns(:example).should == "Just an example"
end
end
end
However, this isn't a good unit test because it's depending on active record and the database, as well as Devise's test helper methods.
So how can I use a mock (or something) to stop my controller redirecting me when I'm trying to test it?
My controller:
class MyController < ApplicationController
before_filter :authenticate_user!, only: [:index]
def index
#example = "Just an example"
end
end
Stub user authentication as advised here in the Devise Wiki.
class TestController < AplicationController
#....
private
def some_method
unless #my_variable.nil?
#...
return true
end
end
end
I want to test some_method directly in controller spec:
require 'spec_helper'
describe TestController do
it "test some_method"
phone = Phone.new(...)
controller.assign(:my_variable,phone) #does not work
controller.send(:some_method).should be_true
end
end
How I can set TestController instance variable #my_variable from controller spec?
When testing private methods in controllers, rather than use send, I tend to use an anonymous controller due to not wanting to call the private method directly, but the interface to the private method (or, in the test below, effectively stubbing that interface). So, in your case, perhaps something like:
require 'spec_helper'
describe TestController do
controller do
def test_some_method
some_method
end
end
describe "a phone test with some_method" do
subject { controller.test_some_method }
context "when my_variable is not nil" do
before { controller.instance_variable_set(:#my_variable, Phone.new(...)) }
it { should be_true }
end
context "when my_variable is nil" do
before { controller.instance_variable_set(:#my_variable, nil) }
it { should_not be_true } # or should be_false or whatever
end
end
end
There's some good discussion on the issue of directly testing private methods in this StackOverflow Q&A, which swayed me towards using anonymous controllers, but your opinion may differ.
instance_eval is a relatively clean way to accomplish this:
describe TestController do
it "test some_method" do
phone = Phone.new(...)
controller.instance_eval do
#my_variable = phone
end
controller.send(:some_method).should be_true
end
end
In this case, using do...end on instance_eval is overkill, and those three lines can be shortened to:
controller.instance_eval {#my_variable = phone}
I don't think you want to access an instance variable from your spec controller, as the spec should test the behaviour, but you can always stub the private method.
In your case it should be something like this (in this example it doesn't make so much sense):
describe TestController do
it "test some_method"
phone = Phone.new(...)
controller.stub(:some_method).and_return(true)
controller.send(:some_method).should be_true
end
end
If this is not what you are looking for take a look at this: How to set private instance variable used within a method test?