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).
Related
I'm using Rails 5.2 with rspec-rails 3.7 and rails-controller-testing gems.
I have a controller that filters results on the index action (yeah, bad practice, legacy code, etc). The problem is I need to test that when I do GET work_orders/index params: { keywords: 'some word' }, the instance variable #work_orders returns filtered results, but if I use assigns(:work_orders) it returns nil. I even tested this assigning the last WorkOrder to that variable, but it still doesn't show the variable in the hash.
work_orders_controller.rb
def index
... some code ...
#work_orders = WorkOrder.last
respond_to do |format|
format.html
end
end
spec/controllers/work_orders_controller_spec.rb
require 'rails_helper'
describe WorkOrdersController do
describe 'GET index' do
it 'collects work orders filtered by courier_ot in #work_orders' do
get :index
expect(assigns(:work_orders)).to be_an_instance_of WorkOrder
end
end
end
That is the simplest example I tested and it doesn't work. The assigns call returns nil, and if I use byebug to inspect the hash, it only has this: {"marked_for_same_origin_verification"=>true}.
The real test should use get :index, params: { keywords: 'asdf' } and test that it gets the filtered work orders.
Any help would be appreciated. Thanks.
I have an issue using my session stored return_to URL in my integration tests.
Because my controller can be accesses from different places I store the referer in the session on the new action and redirect to it in my create action.
cards_controller.rb:
class CardsController < ApplicationController
...
def new
#card = current_user.cards.build
session[:return_to] ||= request.referer
end
def create
#card = current_user.cards.build(card_params)
if #card.save
flash[:success] = 'Card created!'
redirect_to session.delete(:return_to) || root_path
else
render 'new', layout: 'card_new'
end
end
...
end
As I only use the create action in my test I would like to set the session variable in the integration test as I do in my unit test but it doesn't work. I always get redirected to the root path.
cards_interface_test.rb:
class CardsInterfaceTest < ActionDispatch::IntegrationTest
test 'cards interface should redirect after successful save' do
log_in_as(#user)
get cards_path
assert_select 'a[aria-label=?]', 'new'
name = "heroblade"
session[:return_to] = cards_url
assert_difference 'Card.count', 1 do
post cards_path, card: { name: name, icon: 'white-book', color: 'indigo', contents: 'subtitle | Rogue feature'}
end
assert_redirected_to cards_url
follow_redirect!
assert_match name, response.body
assert_select 'td', text: name
end
end
The test fails on the assert_redirected_to line.
I tried to call get new_card_path first but made no difference and now I'm a little bit lost. I don't know if this should basically work but I made a tiny mistake or if I try to do something completely against best practices and should refactor all my interface tests to use something like Selenium or similar tools.
I tried as well to provide the session variable as part of the request like the rails guide describes for functional tests with no effects:
post cards_path, {card: { name: name, icon: 'white-book', color: 'indigo', contents: 'subtitle | Rogue feature' }}, {'return_to' => cards_url}
I don't know if manually setting session is possible in integration tests (guess rather not) but you should be able to set the referer because it's just a normal HTTP header. Headers can be passed as the 3rd parameter to the request method helper (get etc.) in integration tests.
So, I think you should first call the new action with the referer header set (so that it gets into the session) and then the create action should work including the redirect.
class CardsInterfaceTest < ActionDispatch::IntegrationTest
test 'cards interface should redirect after successful save' do
log_in_as(#user)
# visit the 'new' action as if we came from the index page
get new_card_path, nil, referer: cards_url
assert_difference 'Card.count', 1 do
post cards_path, card: { name: name, icon: 'white-book', color: 'indigo', contents: 'subtitle | Rogue feature'}
end
assert_redirected_to cards_url
# ...
end
end
First we try get the new action with the referer set as if we came from the index page (so that the referer can get into the session). The rest of the test stays the same.
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
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.
just a simple question for a render json call I'm trying to test. I'm still learning rspec, and have tried everything and can't seem to get this to work. I keep getting an ActionController::RoutingError, even though I defined the route and the call to the api itself works.
In my controller I have the method:
class PlacesController < ApplicationController
def objects
#objects = Place.find(params[:id]).objects.active
render json: #objects.map(&:api)
end
end
with the render json: #objects.map(&:api), I'm calling the api method in the Object model
class Object
def api
{ id: id,
something: something,
something_else: something_else,
etc: etc,
...
}
end
end
My routes file:
get "places/:id/objects" => "places#objects"
my rspec: spec/controllers/places_controller_spec.rb
describe "objects" do
it "GET properties" do
m = FactoryGirl.create :object_name, _id: "1", shape: "square"
get "/places/#{m._id}/objects", {}, { "Accept" => "application/json" }
expect(response.status).to eq 200
body = JSON.parse(response.body)
expect(body["shape"]).to eq "square"
end
end
I keep getting the error
Failure/Error: get "/places/1/objects", {}, { "Accept" => "application/json" }
ActionController::RoutingError:
No route matches {:controller=>"places", :action=>"/places/1/objects"}
Any help would be much appreciated, thanks.
Because you have the spec in the controllers folder RSpec is assuming it is a controller spec.
With controller specs you don't specify the whole path to the route but the actual controller method.
get "/places/#{m._id}/objects", {}
Should be
get :objects, id: m._id
If you don't want this behaviour you can disable it by setting the config infer_spec_type_from_file_location to false. Or you could override the spec type for this file by declaring the type on the describe
describe "objects", type: :request do - change :request to what you want this spec to be.
Although I recommend using the directory structure to dictate what types of specs you are running.