I have a decorator that receives the application controller as a variable in order to access session variables. Something like:
navigation = NavigationDecorator(user_id, self)
Self being the ApplicationController.
Everything works fine, but now I have to test it and in Rspec I did
navigation = NavigationDecorator(user_id, ApplicationController.new)
During my tests I get:
ActionController::Metal#session delegated to #_request.session, but #_request is nil: #<ApplicationController:0x000000161363f0 #_routes=nil, #_action_has_layout=true, #_headers={"Content-Type"=>"text/html"}, #_status=200, #_request=nil, #_response=nil>
Updating:
I use it like this:
def initialize(user, controller)
#controller = controller
...
end
def retrieve_user_id
user_id = #controller.session[:temporary_id] if #controller.session[:temporary_id]
super
end
I'd propose to stub controller in the test, because since you're writing unit test (you're writing unit test, right?) you want to isolate your system under test (NavigationDecorator) from its dependencies (controller). You can write this test:
describe NavigationDecorator do
context 'user_id'
it 'should take id from session' do
session = { temporary_id: 'temporary' }
controller = instance_double('ApplicationController', session: session)
user = instance_double('User', id: 'user_id')
subject = described_class.new(user, session)
expect(subject.retrieve_user_id).to eq session[:temporary_id]
end
end
end
The test shows us that we have unneeded dependency (controller) and it would be cleaner to pass session right away (if you can, of course):
describe NavigationDecorator do
context 'user_id'
it 'should take id from session' do
session = { temporary_id: 'temporary' }
subject = described_class.new('local id', session)
expect(subject.retrieve_user_id).to eq session[:temporary_id]
end
it 'should take id from user when session is empty' do
controller = instance_double('ApplicationController', session: {})
user = instance_double('User', id: 'user_id')
subject = described_class.new(user, session)
expect(subject.retrieve_user_id).to eq user.id
end
end
end
Related
I have and small project using ruby Jets 3.0.22 and I'm trying to test a function inside a controller using rspec. The issue is that the function uses a session variable set by another controller and I can't set it inside the test so it is always nil.
The function that I'm trying to test is this:
def favorite
favorite = data.items.detect { |item| item['val']['id'] == params[:id] }
unless favorite.blank? || (#song.present? && session[:user].songs.exists?(#song.id))
session[:user].songs << (#song || Song.create!(name: favorite['track']['name'],
id: favorite['track']['id']))
end
render json: { favorite: favorite.present? }, status: 200
end
the session[:user] is always nil
This is the test that I'm trying to do
it 'works' do
get '/favorite/', params: { search_term: :search_term }
expect(response.status).to eq 200
end
I tried to set the session before the test but the object is nil:
request.session['user'] = create(:user) # request doesn't have session
session[:user] = create(:user) # session is nil
# pass as parameter of the request
get '/favorite/', params: { search_term: :search_term }, session: {user: create(:user)}
versions:
Rspec 3.10
Ruby 2.7.4p191
Jets 3.0.22
Please make sure that your spec file is a controller spec, because for controller specs rspec adds helper like session for you.
If your want to access the session outside the controller spec you can try adding include ::RSpec::Rails::ControllerExampleGroup which should suffice (Not tested).
I am trying to write Rspec test case for the submit method in the app/controllers.sample.rb file.
User class is defined in the lib/classes folder. User object is created in the session.rb file in app/controllers/concerns which is autoloaded during creation of new session.
The user method in the session.rb gets the user parameters from another API.
Here I am finding it difficult to create the User object using Rspec, it is always returning error at
list = user.get_list
I have given the sample set of code I have written for the test case.
Could anyone help how to instantiate the User object in concerns/session.rb from rspec ?
app/controllers/concerns/session.rb
def user
if user
user
else
begin
rest_resource = RestClient::Resource.new(ENV['SESSION_API'], :verify_ssl => false)
data = rest_resource.get Authorization: request.headers['Authorization']
rescue RestClient::Exception => e
#error = JSON.parse(e.response, symbolize_names: true)
return nil
end
self.user = User.new(current_user,request.headers['Authorization'] )
end
end
spec/controllers/rspec_sample.rb
describe "Submit" do
it "Submit and expects to succeed" do
allow_any_instance_of(Concerns::Session).to receive(:current_user).and_return(name: "test")
allow_any_instance_of(Concerns::Session).to receive(:user).and_return(name: "test")
post :submit, params
expect(response).to have_http_status(200)
end
end
app/controllers/sample.rb
def submit
list = user.get_list
end
lib/classes/user.rb
class User
def list
return values
end
end
I'm currently working on a team that requires 100% code coverage and I cannot for the life of me get hit this single line method to get my current coverage up to 100%.
I have a base controller that looks like this which multiple other controller extend from.
module Owners
module Pets
class BaseController < Owners::ApplicationController
private
def current_pet
current_owner.all_pets.find(params[:pet_id])
end
end
end
end
My spec for this controller looks like this.
require 'rails_helper'
Rails.describe Owners::Pets::BaseController, type: :controller do
routes { Owners::Engine.routes }
controller Owners::Pets::BaseController do
def index
current_pet
end
end
let(:user) { double :user, id: 1, owner: nil }
before { sign_in(user) }
before do
allow(request.env['warden']).to receive(:user).and_return user
allow(Owners::User).to receive(:find).with(user.id).and_return user
end
context 'with current owner and pet' do
let(:owner) { create :owner }
let(:pet) { create(:owner_pet, owner: owner) }
describe '#current_pet' do
before do
allow(controller).to receive(:current_pet).and_return pet
routes.append { get 'index' => 'owners/pets/base#index' }
end
it do
get :index
is_expected.to eq pet
end
end
end
end
The spec is failing with the error "No route matches {:action=>"index", :controller=>"owners/pets/base"}" Routes.append should add the proper route, correct?
Update: By moving my routes { Owners::Engine.routes } line above the anonymous controller it no longer throws an error related to routes. However now it is comparing pet with the actual controller class. The output is too long to paste here but it's essentially:
expected: #<Owners::Pet>
got: #<#<Class:0x007fc65c860518>:0x007fc65f83a248>
with a whole bunch of attributes and methods.
This test has no value. You're stubbing the very method that you're testing. Even if the method body of #current_pet raised an exception, the test would still pass.
Generally, it's best to avoid testing private methods directly. You should be able to test this method indirectly via one of the classes which inherits from Owners::Pets::BaseController.
When you use the syntax it { is_expected.to ... } Rspec must infer what "it" is based on the test itself. The subject method can be used to explicitly specify what "it" is; in cases where subject is not present, Rspec will instantiate a new instance of the class which is being tested. In your case that would be the controller itself.
Try setting the subject explicitly inside the context of the #current_pet block.
For example,
context 'with current owner and pet' do
let(:owner) { create :owner }
let(:pet) { create(:owner_pet, owner: owner) }
describe '#current_pet' do
before do
allow(controller).to receive(:current_pet).and_return pet
routes.append { get 'index' => 'owners/pets/base#index' }
end
# set this to whatever you want "is_expected.to" to be
subject { controller.current_pet }
it do
get :index
is_expected.to eq pet
end
end
end
Obligatory Note: I have to agree with other posters that this test is not very useful. Conventional wisdom is to only test public methods (private methods get tested by their usage within a public method).
This is my controller:
class PlansController
def use_new_card
#user = User.find_by_id(new_card_params[:user_id])
if new_stripe_card
...
end
private
def new_card_params
params.permit(:user_id, :stripeToken)
end
def new_stripe_card
StripeService.new({user_id: #user.id, customer_id: #user.stripe_customer_id, source: new_card_params[:stripeToken]}).new_card
end
end
I'm trying to write a controller spec that tests that when the proper parameters are POST to the use_new_card method, then new_stripe_card's StripeService.new gets these parameters appropriately.
My spec looks like this so far:
describe "when proper params are passed" do
before do
#user = User.create(...)
end
it "should allow StripeService.new to receive proper parameters" do
StripeService.should_receive(:new).with({user_id: #user.id, customer_id: #user.stripe_customer_id, source: "test"})
post :use_new_card, user_id: #user.id, stripeToken: "test"
end
end
But with that I'm getting
Failure/Error: post :use_new_card, user_id: #user.id, stripeToken: "test"
NoMethodError:
undefined method `new_card' for nil:NilClass
Totally fair, but I'm not sure how to fix this... I can't just stub new_card because a stubbed method on a nil object will still throw this error (I tried adding a StripeService.any_instance.stub(:new_card).and_return(true) already into the before block)
Stubbed methods return nil by default. Use and_return to specify the value returned by the stub::
StripeService.should_receive(:new).and_return(whatever)
or using the newer syntax
expect(StripeService).to receive(:new).and_return(whatever)
EDIT
Pardon my hand-waving. Your stub must return an object that will act like an instance of StripeService to the extent required for the purposes of the test. So for example:
let(:new_card) { double }
let(:new_stripe_service) { double(new_card: new_card) }
...
expect(StripeService).to receive(:new).and_return(new_stripe_service)
Now when the test refers to new_stripe_service, RSpec will return a test double that has a method stub named #new_card, which itself is a double. You can add whatever additional stubs you need to make the test pass. Alternatively, you can return an actual instance of StripeService for new_stripe_service.
I'm testing to make sure that a created user is assigned to my instance variable #user. I understand what get means, but I'm not sure what to write for the test. I'm returning with an argument error for a bad URI or URL. What's wrong with my test and how do I fix it?
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :users
# I'm confused on what this line above means/does. What does the hash :users refer
#to
assigns[:user].should == [p]
end
The expected URI object or string error refers to get :users and the error is as follows
Failure/Error get :users
ArgumentError:
bad argument: (expected URI object or URI string)
I guess that what you want is
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :show, id: p.id
assigns(:user).should == p
end
The line you were not sure about checks that content of the assigned variable (#user) in the show view of the user p, is equal to the p user you just created more information there
what action are you trying to test? usually, for creation, you need to test that the controller's "create" action creates a user and assigns an #user variable
I would test it this way:
describe 'POST create' do
it 'creates a user' do
params = {:user => {:name => 'xxx', :lastname => 'yyy'}}
User.should_receive(:create).with(params)
post :create
end
it 'assigns the user to an #user instance variable' do
user = mock(:user)
User.stub!(:create => user)
post :create
assigns(:user).should == user
end
end
notice that I stub/mock all user methods, since you are testing a controller you don't have to really create the user, you only test that the controller calls the desired method, the user creation is tested inside the User model spec
also, I made 2 tests (you should test only 1 thing on each it block if possible, first it test that the controller creates a user, then I test that the controller assigns the variable
I'm assuming your controller is something like this:
controller...
def create
#user = User.create(params[:user])
end
which is TOO simple, I guess you have more code and you should test that code too (validations, redirects, flash messages, etc)