Rspec: testing assignment of instance variable - ruby-on-rails

Using Rspec with Factory Girl. Trying to check out what data is being assigned in my controller (and test against it). Every post I've read says I should be able to get something out of assigns() but it keeps returning nill
Controller
def index
#stickies = Sticky.where(:user_id => current_user.id)
end
Spec
it "should assign stickies" do
foo = assigns(:stickies)
puts "foo = #{foo}"
end
Output
foo =
Am I using the wrong syntax? Is there a better way to do this? Thanks!!

You have to invoke the action first
describe StickiesController do
describe "GET index" do
it "should assign stickies" do
get :index
assigns(:stickies).should_not be_nil
end
end
end

If you are using the rspec > 2.99 you can use:
expect(assigns(:stickies)).not_to be_nil

Related

Assigns method returns nil in RSpec

I am doing my testing with mocks and stubs but I keep getting nil in my output. I am not sure if it's the problem with the assigns method. Could I also know how I should debug this kind of problem? I am using rspec-rails 3.5.2 Thank you.
The failing test:
describe 'guest user' do
describe 'GET index' do
let(:achievement) { instance_double(Achievement) }
before do
allow(Achievement).to receive(:get_public_achievements) { [achievement] }
end
it 'assigns public achievements to template' do
get :index
expect(assigns(achievement)).to eq([achievement])
end
end
end
The index action in the controller
def index
#achievements = Achievement.get_public_achievements
end
The get_public_achievements in the achievement model
def self.get_public_achievements
// empty method it's fine
end
The error:
1) AchievementsController guest user GET index assigns public achievements to template
Failure/Error: expect(assigns(achievement)).to eq([achievement])
expected: [#<InstanceDouble(Achievement) (anonymous)>]
got: nil
(compared using ==)
assigns is keyed by symbols. Should be
expect(assigns(:achievements)).to eq([achievement])

Set request variables for functional testing of controllers in rails with minitest

When doing functional tests for controllers in rails how can I provide dynamic application instance variables to my test which live in the request.
I have a #station object that is initialized in a before action in my application controller (available in all other controllers), however the #station object is defined by the domain entry of the user e.g.: blue.mydomain.com. So it could have 3 different flavors, and the controller actions params[:id] are only valid for a certain flavor.
Further if I don't give my #station a flavor for my test environment it will fail utterly:
(Here code from a helper that gets called in a before action in my application_controller.rb)
def init_station
if Rails.env == "test"
#station=Station.new('blue')
else
#station=Station.new(domain_flavor_picker)
end
end
ApplicationController
....
before_action :init_station
.....
end
Thus I can only test for 'blue' or switch the flavor in my before action and then mock for different id!
test:
describe MyController do
before do
#id="10215d8da3f4f278cec747f09985b5528ec9e"
end
it "should get index action" do
p assigns(:station) # is nil
get :artist_biography, id: #id, locale: I18n.available_locales.sample
assert_response :success
assert_not_nil assigns(:meta)
assert_not_nil assigns(:nav)
assert_not_nil assigns(:content)
end
end
As you can see I am also in need of providing a locale variable. I managed to mix up that call with I18n.available_locales.sample
How can I dynamically switch or manipulate my #station instance variable?
My issue was that I needed to provide minitest with an initial host! From #smathy answer I knew that I needed a Mock Request for the Controller!
Turns out that it is quite easy to set it in MiniTest if you know how!
Rails provides an ActionDispatch::TestRequest object which in itself seems to be a Rack::MockRequest object:
DEFAULT_ENV = Rack::MockRequest.env_for('/', 'HTTP_HOST' => 'test.host', 'REMOTE_ADDR' => '0.0.0.0', 'HTTP_USER_AGENT' => 'Rails Testing' )
So all I had to do in my test was:
before do
#request.env['HTTP_HOST'] = %w(blue.mydomain.com red.mydomain.com green.mydomain.com).sample
end
to initialize my #station object with a sample of flavored domains.
assigns :station will only return a value after you do the request, ie. after the get line. Until you've done the request none of your controller code has been run for that test.
You also shouldn't use #vars in rspec, use let instead, and a few other things that I've shown below, many of which I learned from BetterSpecs
The Crux of your Issue
Assuming that domain_flavor_picker is a method in your controller then you should just mock that so you can different tests for the different return values of it. So, this shows the context for one of the return values of domain_flavor_picker, but you'd add other contexts for other values:
describe MyController do
let(:id) { "10215d8da3f4f278cec747f09985b5528ec9e" }
describe "GET /artist_biography" do
context "when domain_flavor is blue" do
before do
allow(controller).to receive(:domain_flavor_picker) { "blue" } # <-- MOCK!
end
context "when valid id" do
before { get :artist_biography, id: id, locale: I18n.available_locales.sample }
subject { response }
it { is_expected.to be_success }
it "should assign :meta" do
expect(assigns :meta).to be_present # better to have an actual matcher here
end
it "should assign :nav" do
expect(assigns :nav).to be_present # ditto
end
it "should assign :content" do
expect(assigns :content).to be_present # ditto
end
end
end
end
end

Testing controllers with RSpec: Can't read variables

Having trouble testing variable values from a controller using RSpec.
Relevant controller code:
class ToysController < ApplicationController
def claim
toy = Toy.find(params[:toy_id])
current_user.toys << toy
toy.status = "claimed"
render :index
end
end
This definitely works -- I know because I puts toy.inspect after it happens, and it's fine. But I can't test it. Here's what my current test looks like, after a lot of messy attempts:
require 'spec_helper'
describe ToysController do
describe "GET 'claim'" do
let(:james) {create(:user)}
let(:toy) {create(:toy)}
before do
OmniAuth.config.mock_auth[:google] = {
uid: james.uid
}
session[:user_id] = james.id
end
it "can be claimed by a user" do
get :claim, toy_id: toy.id
assigns(:toy).user.should eq james.id
end
end
end
When I run the test, I get all sorts of errors on assigns(:toy).user.should indicating that toy is Nil. I've tried messing with the assigns syntax in lots of ways, because I was unable to find the docs for it.
What am I doing wrong? What's the right way to see what the controller does with the user and the toy passed to it?
Edit: Trying to phase over to instance variables, but it still doesn't do the trick. Here's my code again with instance variables (different var names, same results):
Ideas controller:
def claim
#idea = Idea.find(params[:idea_id])
current_user.ideas << #idea
#idea.status = "claimed"
render :index
end
Test:
describe "GET 'claim'" do
let(:james) {create(:user)}
let(:si_title) {create(:idea)}
before do
OmniAuth.config.mock_auth[:google] = {
uid: james.uid
}
session[:user_id] = james.id
end
it "can be claimed by a user" do
get :claim, idea_id: si_title.id
puts assigns(si_title).inspect
end
end
Output: nil.
Solved! Test now reads like this:
describe "GET #claim" do
let(:james) {create(:user)}
let(:si_title) {create(:idea)}
before do
OmniAuth.config.mock_auth[:google] = {
uid: james.uid
}
session[:user_id] = james.id
end
it "can be claimed by a user" do
get :claim, idea_id: si_title.id
assigns(:idea).user_id.should eq james.id
end
end
My mistakes:
Not using a colon to prefix the instance variable name in the assigns.
Using the incorrect variable name in the assigns.
Try replacing toy variable in Controller with #toy. assigns has access only to instance variables.

Accessing session from a helper spec in rspec

I have a method in my ApplicationHelper that checks to see if there are any items in my basket
module ApplicationHelper
def has_basket_items?
basket = Basket.find(session[:basket_id])
basket ? !basket.basket_items.empty? : false
end
end
Here is my helper spec that I have to test this:
require 'spec_helper'
describe ApplicationHelper do
describe 'has_basket_items?' do
describe 'with no basket' do
it "should return false" do
helper.has_basket_items?.should be_false
end
end
end
end
however when I run the test i get
SystemStackError: stack level too deep
/home/user/.rvm/gems/ruby-1.9.3-p194/gems/actionpack-3.2.8/lib/action_dispatch/testing/test_process.rb:13:
From debugging this i see that session is being accessed in ActionDispatch::TestProcess from #request.session, and #request is nil. When i access the session from my request specs #request is an instance of ActionController::TestRequest.
My question is can I access the session object from a helper spec? If I can, how? And if I cant what is the best practice to test this method?
****UPDATE****
This was down to having include ActionDispatch::TestProcess in my factories. Removing this include sorts the problem.
can I access the session object from a helper spec?
Yes.
module ApplicationHelper
def has_basket_items?
raise session.inspect
basket = Basket.find(session[:basket_id])
basket ? !basket.basket_items.empty? : false
end
end
$ rspec spec/helpers/application_helper.rb
Failure/Error: helper.has_basket_items?.should be_false
RuntimeError:
{}
The session object is there and returns an empty hash.
Try reviewing the backtrace in more detail to find the error. stack level too deep usually indicates recursion gone awry.
You are testing has_basket_items? action in ApplicationHelper, which check a specfic basket with a basket_id in the baskets table, so you should have some basket objects in your test which you can create using Factory_Girl gem.
Hers's an example :-
basket1 = Factory(:basket, :name => 'basket_1')
basket2 = Factory(:basket, :name => 'basket_2')
You can get more details on How to use factory_girl from this screen cast http://railscasts.com/episodes/158-factories-not-fixtures
It will create a Factory object in your test database. So, basically you can create some factory objects and then set a basket_id in session to check for its existence like this :
session[:basket_id] = basket1.id
So, your test should be like this :-
require 'spec_helper'
describe ApplicationHelper do
describe 'has_basket_items?' do
describe 'with no basket' do
it "should return false" do
basket1 = Factory(:basket, :name => 'basket_1')
basket2 = Factory(:basket, :name => 'basket_2')
session[:basket_id] = 1234 # a random basket_id
helper.has_basket_items?.should be_false
end
end
end
end
Alternatively, you can check for a basket_id which is being created by factory_girl to be_true by using :
session[:basket_id] = basket1.id
helper.has_basket_items?.should be_true

Rspec: how to spec request.env in a helper spec?

In my helper module, I have:
def abc(url)
...
if request.env['HTTP_USER_AGENT']
do something
end
end
In my spec file, I have:
describe "#abc" do
before(:each) do
#meth = :abc
helper.request.env['HTTP_USER_AGENT'] = "..."
end
it "should return the webstart jnlp file" do
#obj.send(#meth, "some_url").should ....
end
end
When I run the spec I have this error:
undefined local variable or method `request' for <ObjectWithDocHelperMixedIn:0x00000103b5a7d0>
How do I stub for request.env['...'] in my specs?
Thanks.
If you're using rspec-rails, you might be able to use controller.request in your helper tests.
Well, you've almost nothing to do:
before(:each) do
#meth = :abc
request.env['HTTP_USER_AGENT'] = "..."
end
I just gave this another try and this passes:
#in helper
def foo
request.env['HTTP_USER_AGENT']
end
#spec
it "foo" do
helper.request.env['HTTP_USER_AGENT'] = 'foo'
expect(helper.foo).to eq 'foo'
end
You can override user-agent set in the request env by doing the following.
before(:each) do
#meth = :abc
helper.request.user_agent = 'something else'
end
Then, in your spec:
it "does stuff" do
expect(helper.send(#meth, "some_url")).to # ...
end
for those who use request specs instead of controller specs and want to set request.env can do it like this:
Rails.application.env_config["whatever"] = "whatever"
this will make request.env["whatever"] available in your controllers with value you gave it in your specs.
Try this:
stub(request).env { {"HTTP_USER_AGENT" => "Some String"} }

Resources