Testing instance variables in controller with RSpec - ruby-on-rails

Given a controller like this where it creates several instance variables for use by the view, would you generally test that each of those get set properly? It seems like you would want to, but it also seems a little it could be a bit tricky. What's the right approach?
class StaffsController < ApplicationController
def index
set_index_vars
#all_staff = Staff.find_staff_for_business_all_inclusive(current_business_id)
respond_to do |format|
format.html { render :action => "index", :locals => { :all_staff => #all_staff, :all_services => #all_services, :new_vacation => #new_vacation } }
end
end
def set_index_vars
#days_of_week = days_of_week
#first_day_of_week = DefaultsConfig.first_day_of_week
#all_services = Service.services_for_business(current_business_id)
#new_vacation = StaffVacation.new
#has_hit_staff_limit = current_user_plan.has_hit_staff_limit?
end
end
The code is also posted at https://gist.github.com/1018190

If you're going to write a controller spec, then yes, by all means test that the instance variables are assigned. Much of the 'trickiness' can come from dependencies on other models/libraries, so stub out those method calls:
# air code
Staff.stub(:find_staff_for_business_all_inclusive) {array_of_staff}
controller.stub(:days_of_week) {['Monday','Tuesday',....etc...]}
DefaultsConfig.stub(:first_day_of_week) {"Monday"}
Service.stub(:services_for_business).with(some_value_for_the_current_business_id).\
and_return(some_relevant_value)
StaffVacation.stub(:new) {something_meaningful}
controller.stub_chain(:current_user_plan,:has_hit_staff_limit?) {false}
get :index
assigns(:days_of_week).should == ['Monday','Tuesday',....etc...]
# ...etc...

I would split it up as follows: test that the index calls the correct method. Then test whether the method works.
So something like
describe StaffsController do
describe "GET #index" do
it "calls set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
# and your usual tests ...
end
describe "#set_index_vars" do
before(:each) do
# stub out the code not from this controller
controller.stub_chain(:current_user_plan, :has_hit_staff_limit?).and_return(false)
.. etc ..
controller.set_index_vars
end
it { assigns(:days_of_week).should == controller.days_of_week }
it { assigns(:has_hit_staff_limit).should be_false
# etc ..
end
end
Hope this helps.

So long as you have good coverage around your method, you can test that your method is being called at the right times, with the right values etc. Something like:
describe StaffsController do
describe "GET #index" do
it "should call set_index_vars" do
controller.should_receive(:set_index_vars)
get :index
end
end
describe "#set_index_vars" do
it "should assign instance variables with correct values" do
# or wtv this is supposed to do
get :index
assigns(:days_of_week).should == controller.days_of_week
# etc ..
end
end
end

Related

Rspec: how to test Service Object method "call" which is called in Controller action create?

Can somebody help me with rspec testing method call in Service Object?
class UserEntitiesController < ApplicationController
def create
#result = UserEntities::Create.new(params).call
return render '/422.json.jbuilder', status: :unprocessable_entity unless #result
end
here is the service objects:
module UserEntities
class Create
attr_accessor :params
def initialize(params)
#params = params
end
def call
#user_entity = UserEntity.new(user_entity_params)
set_time
if #user_entity.save
#user_entity
else
error_result
end
end
private
def error_result
false
end
def user_entity_params
#params.require(:user_entity).permit(:information,
:destroy_option,
:reviews)
end
def set_time
if #params[:available_days].present?
#user_entity.termination = Time.now + #params[:available_days].days
end
end
end
end
I tried to find information how to do this, but there are not so many.
Also i read some
You can certainly write a unit test to test the Service Object standalone
In this case, create a file spec/services/user_entities/create_spec.rb
describe UserEntities::Create do
let(:params) { #values go here }
context ".call" do
it "create users" do
UserEntities::Create.new(params).call
# more test code
end
# more tests
end
end
Later in the controller tests, if you are planning to write such, you do not need to test UserEntities::Create instead you can just mock the service object to return the desired result
describe UserEntitiesController do
before do
# to mock service object in controller test
allow(UserEntities::Create).to receive(:new)
.and_return(double(:UserEntities, call: "Some Value"))
end
# controller tests go here
end
As a supplement to #bibin answer.
If you want to mock some instance's method renturn:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_return("some value")
if you want to raise a eror:
allow_any_instance_of(UserEntities::Create).to receive(:call).and_raise("boom")

How to test that a class is called in a controller method with RSpec

I am testing my controller to ensure that a library class is called and that the functionality works as expected. NB: This might have been asked somewhere else but I need help with my specific problem. I would also love pointers on how best to test for this.
To better explain my problem I will provide context through code.
I have a class in my /Lib folder that does an emission of events(don't mind if you don't understand what that means). The class looks something like this:
class ChangeEmitter < Emitter
def initialize(user, role, ...)
#role = role
#user = user
...
end
def emit(type)
case type
when CREATE
payload = "some payload"
when UPDATE
payload = "some payload"
...
end
send_event(payload, current_user, ...)
end
end
Here is how I am using it in my controller:
class UsersController < ApplicationController
def create
#user = User.new(user_params[:user])
if #user.save
render :json => {:success => true, ...}
else
render :json => {:success => false, ...}
end
ChangeEmitter.new(#user, #user.role, ...).emit(ENUMS::CREATE)
end
end
Sorry if some code doesn't make sense, I am trying to explain the problem without exposing too much code.
Here is what I have tried for my tests:
describe UsersController do
before { set_up_authentication }
describe 'POST #create' do
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
post :create, user: user_params
expect(response.status).to eq(200)
// Here is the test for the emitter
expect(ChangeEmitter).to receive(:new)
end
end
end
I expect the ChangeEmitter class to receive new since it is called immediately the create action is executed.
Instead, here is the error I get:
(ChangeEmitter (class)).new(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
What am I missing in the above code and why is the class not receiving new. Is there a better way to test the above functionality? Note that this is Rspec. Your help will be much appreciated. Thanks.
You need to put your expect(ChangeEmitter).to receive(:new) code above the post request. When you are expecting a class to receive a method your "expect" statement goes before the call to the controller. It is expecting something to happen in the future. So your test should look something like:
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
expect(ChangeEmitter).to receive(:new)
post :create, user: user_params
expect(response.status).to eq(200)
end
EDIT
After noticing that you chain the "emit" action after your call to "new" I realized I needed to update my answer for your specific use case. You need to return an object (I usually return a spy or a double) that emit can be called on. For more information on the difference between spies and doubles check out:
https://www.ombulabs.com/blog/rspec/ruby/spy-vs-double-vs-instance-double.html
Basically a spy will accept any method called on it and return itself whereas with a double you have to specify what methods it can accept and what is returned. For your case I think a spy works.
So you want to do this like:
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
emitter = spy(ChangeEmitter)
expect(ChangeEmitter).to receive(:new).and_return(emitter)
post :create, user: user_params
expect(response.status).to eq(200)
end

Dynamic rendering with Rails 5 and Rspec

I am trying to test that my controller renders the correct status codes via request specs. This application uses a bit of meta-programming with dynamic class names to render views. How can I stub the render call below to return the correct status code desired for my spec?
Rspec Spec Snippet
context 'renders 200' do
let(:provider_slug) { create(:provider, :active).slug }
let(:template) { "providers/v1/#{provider_slug}/new" }
let(:layout) { "providers/v1/#{provider_slug}" }
let(:provider_double) do
instance_double(
ProviderRouter,
valid?: true,
form_model: ProviderFormModel
)
end
before do
allow(ProviderRouter).
to receive(:new).with(version: 'V1', provider_slug: provider_slug).
and_return(provider_double)
allow(described_class).to receive(:render_new_form).and_return(true)
get route
end
it 'true' do
expect(response.status).to be(200)
end
end
Controller Snippet
class V1::ProvidersController < ApplicationViewController
before_action :init_provider, :init_form_types, :validate_provider
def new
#provider_form_model = provider_router.form_model.new
render_new_form
end
private
attr_reader :provider_slug, :provider_path, :provider_router, :provider_model
def render_new_form
render template: "providers/v1/#{provider_slug}/new", layout: "providers/v1/#{provider_slug}"
end
Updated for Answer Below
context 'renders 200' do
let(:provider_slug) { create(:provider, :active).slug }
let(:provider_double) do
instance_double(
ProviderRouter,
valid?: true,
form_model: ProviderFormModel
)
end
before do
allow(ProviderRouter).
to receive(:new).with(version: 'V1', provider_slug: provider_slug).
and_return(provider_double)
allow(controller).to receive(:provider_slug).and_return(provider_slug)
allow(controller).to receive(:render).and_call_original
allow(controller).to receive(:render).
with(template: "providers/v1/#{provider_slug}/new", layout: "providers/v1/#{provider_slug}") do
controller.render plain: '200 [OK]'
end
get "/v1/providers/#{provider_slug}"
end
it 'true' do
expect(response.status).to be(200)
end
end
The formal answer would be that you shouldn't stub it as you would be stubbing behaviour of the object under test.
You should rather provide a provider_slug to be used for the test.
Technically, it would be possible to do this:
allow(controller) # controller is the instance of the ProvidersController used under the hood
.to_receive(:render_new_form)
.and_return("some bogus value")
But this would lead to rails trying to render the default template as no rendering has happened yet. It would thus be helpful to actually call the render method which can be achived by:
# we call the render method in our stub and thus have to be able to call the original
allow(controller)
.to receive(:render)
.and_call_original
allow(controller)
.to_receive(:render) # not render_new_form
.with(template: anything, layout: anything) do
controller.render plain: '200 [OK]'
end

`rspec` - comparing two arrays' lengths against each other

Having 2 arrays in index method, I want to test the equality of the arrays' lengths but it's not working (the lengths of the arrays are equal after testing manually)
def index
#countries = get_countries()
#capitals = get_capitals()
end
Rspec file:
describe CountriesController do
describe 'index' do
it 'countries.length == capitals.length' do
expect(assigns(countries.length)).to eq (assigns(capitals.length))
end
end
end
Doesn't look like you are making a request to that action... that is.. where is the get :index call?
It should be like this:
describe CountriesController do
describe 'index' do
it 'countries.length == capitals.length' do
get :index
expect(assigns(:countries).length).to eq assigns(:capitals).length
end
end
end

How to assign or pass value to the instance variable in controller from rspec controller?

Following is my rails controller:
class MyController < ApplicationController
def index
#client = (current_company.clients.size || 0) >= current_company.subscription.clients # it returns true or false
begin
#obj = Class.all
respond_to do |format|
format.html # index.html.erb
end
rescue
end
end
end
Following is my rspec code under (spec/controller):
require 'spec_helper'
describe MyController do
describe "GET index" do
it "populates an array of data" do
current_company = mock_model(CompaniesUser)
clients = mock_model(Client)
get :index
.
.
end
end
end
After execution it provide me following error:
Failures:
1) MyController GET index populates an array of clients
Failure/Error: get :index
Double "Company_1" received unexpected message :clients with (no args)
# ./app/controllers/my_controller.rb:20:in `index'
# ./spec/controllers/my_controller_spec.rb:28:in `block (3 levels) in <top (required)>'
So how to do this association about current_compnay.clients.size in rspec controller? It provides an error due to not getting value current_company.clients.size in controller's index method from spec.
Not sure if I understand your question correctly. Are you looking for something like this?
it "populates an array of data" do
controller.stub(:current_company) {
mock_model(CompaniesUser, clients: [mock_model(Client)])
}
get :index
# ...
Some changes after your comment:
let(:client) { mock_model(Client, :id => 1)}
let(:company) { mock_model(Company, :id => 1, :clients => [client])}
before { controller.stub(:current_company).and_return(company) }
it "populates an array of data" do
get :index
# ...
disclaimer: please do not swallow errors!
what is that begin rescue end part for? please go ahead and remove that. it hides any error that occurs when rendering a template!
what is that #obj = Class.all is that pseudo code? if you add pseudo code, make a note about that!
if you have such complex logic in your controller, it would be a good idea to move it into a method of that class. so (current_company.clients.size || 0) >= current_company.subscription.clients might be refactored to a call of current_company.has_not_enough_clients or whatever your business logic should name it.
then go ahead and stub that method or use a test-double for only that specific model.
Problem resolved as follow:
at start of controller spec:
let(:current_company) {mock_model(CompanyUser, :id => 1, clients: [mock_model(Client)])}
now you can access it as "current_company.clients.size" gives "1"

Resources