`rspec` - comparing two arrays' lengths against each other - ruby-on-rails

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

Related

Testing Rspec after_action that changes counter

I have my controller which I'm trying to test.
class ShortLinksController < ApplicationController
after_action :increment_view_count, only: :redirect_to_original_url
def redirect_to_original_url
link = ShortLink.find(params[:short_url])
redirect_to "http://#{link.original_url}"
end
private
def increment_view_count
ShortLink.increment_counter(:view_count, params[:short_url])
end
end
This is the route for redirect_to_original_url:
get 's/:short_url', to: 'short_links#redirect_to_original_url', as: 'redirect_to_original_url'
And my Rspec tests:
describe "#redirect_to_original_url" do
let(:short_link) {ShortLink.create(original_url: 'www.google.com')}
subject {get :redirect_to_original_url, params: {short_url: short_link.id}}
it 'should increment the count by 1 original url is visited' do
expect {subject}.to change{ short_link.view_count }.by(1)
end
end
For some reason I get the following error when I run my tests:
expected `short_link.view_count` to have changed by 1, but was changed by 0
My logic works as I can see it increments that individual link view_count by 1, but not my test.
Check the default value of view_count when you create a object for ShortLink model using,
let(:short_link) {ShortLink.create(original_url: 'www.google.com')}
//Creating object
it 'should have value 0 when shortlink object is created' do
expect(short_link.view_count).to eq(0)
end
If this example fails then create object with default value for view_count as,
let(:short_link) {ShortLink.create(original_url: 'www.gmail.com',view_count: 0)}
Meanwhile as Jake Worth said your rspec test is not calling,
after_action :increment_view_count, only: :redirect_to_original_url
in your controller(check this by calling increment_view_count function from your redirect_to_original_url function and run your tests).
Since you created short_link variable you need to reload it to check that value was changed. Unless reloading it stores previous value.
expect { subject }.to change{ short_link.reload.view_count }.by(1)

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

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"

Testing instance variables in controller with RSpec

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

Testing rendering of a given layout with RSpec & Rails

Is it possible to test the use of a given layout using RSpec with Rails, for example I'd like a matcher that does the following:
response.should use_layout('my_layout_name')
I found a use_layout matcher when Googling but it doesn't work as neither the response or controller seem to have a layout property that matcher was looking for.
David Chelimsky posted a good answer over on the Ruby Forum:
response.should render_template("layouts/some_layout")
This works for me with edge Rails and edge RSpec on Rails:
response.layout.should == 'layouts/application'
Shouldn't be hard to turn this into a matcher suitable for you.
There's already a perfectly functional matcher for this:
response.should render_template(:layout => 'fooo')
(Rspec 2.6.4)
I found an example of how to write a use_layout matcher that will do just that. Here's the code in case that link goes away:
# in spec_helper.rb
class UseLayout
def initialize(expected)
#expected = 'layouts/' + expected
end
def matches?(controller)
#actual = controller.layout
##actual.equal?(#expected)
#actual == #expected
end
def failure_message
return "use_layout expected #{#expected.inspect}, got #
{#actual.inspect}", #expected, #actual
end
def negeative_failure_message
return "use_layout expected #{#expected.inspect} not to equal #
{#actual.inspect}", #expected, #actual
end
end
def use_layout(expected)
UseLayout.new(expected)
end
# in controller spec
response.should use_layout("application")
I had to write the following to make this work:
response.should render_template("layouts/some_folder/some_layout", "template-name")
Here is an updated version of the matcher. I've updated it to conform to the latest version of RSpec. I've added the relevant read only attributes and remove old return format.
# in spec_helper.rb
class UseLayout
attr_reader :expected
attr_reader :actual
def initialize(expected)
#expected = 'layouts/' + expected
end
def matches?(controller)
if controller.is_a?(ActionController::Base)
#actual = 'layouts/' + controller.class.read_inheritable_attribute(:layout)
else
#actual = controller.layout
end
#actual ||= "layouts/application"
#actual == #expected
end
def description
"Determines if a controller uses a layout"
end
def failure_message
return "use_layout expected #{#expected.inspect}, got #{#actual.inspect}"
end
def negeative_failure_message
return "use_layout expected #{#expected.inspect} not to equal #{#actual.inspect}"
end
end
def use_layout(expected)
UseLayout.new(expected)
end
Additionally the matcher now also works with layouts specified at the controller class level and can be used as follows:
class PostsController < ApplicationController
layout "posts"
end
And in the controller spec you can simply use:
it { should use_layout("posts") }
Here's the solution I ended up going with. Its for rpsec 2 and rails 3.
I just added this file in the spec/support directory.
The link is: https://gist.github.com/971342
# spec/support/matchers/render_layout.rb
ActionView::Base.class_eval do
unless instance_methods.include?('_render_layout_with_tracking')
def _render_layout_with_tracking(layout, locals, &block)
controller.instance_variable_set(:#_rendered_layout, layout)
_render_layout_without_tracking(layout, locals, &block)
end
alias_method_chain :_render_layout, :tracking
end
end
# You can use this matcher anywhere that you have access to the controller instance,
# like in controller or integration specs.
#
# == Example Usage
#
# Expects no layout to be rendered:
# controller.should_not render_layout
# Expects any layout to be rendered:
# controller.should render_layout
# Expects app/views/layouts/application.html.erb to be rendered:
# controller.should render_layout('application')
# Expects app/views/layouts/application.html.erb not to be rendered:
# controller.should_not render_layout('application')
# Expects app/views/layouts/mobile/application.html.erb to be rendered:
# controller.should_not render_layout('mobile/application')
RSpec::Matchers.define :render_layout do |*args|
expected = args.first
match do |c|
actual = get_layout(c)
if expected.nil?
!actual.nil? # actual must be nil for the test to pass. Usage: should_not render_layout
elsif actual
actual == expected.to_s
else
false
end
end
failure_message_for_should do |c|
actual = get_layout(c)
if actual.nil? && expected.nil?
"expected a layout to be rendered but none was"
elsif actual.nil?
"expected layout #{expected.inspect} but no layout was rendered"
else
"expected layout #{expected.inspect} but #{actual.inspect} was rendered"
end
end
failure_message_for_should_not do |c|
actual = get_layout(c)
if expected.nil?
"expected no layout but #{actual.inspect} was rendered"
else
"expected #{expected.inspect} not to be rendered but it was"
end
end
def get_layout(controller)
if template = controller.instance_variable_get(:#_rendered_layout)
template.virtual_path.sub(/layouts\//, '')
end
end
end
response.should render_template("layouts/some_folder/some_layout")
response.should render_template("template-name")
controller.active_layout.name works for me.
Shoulda Matchers provides a matcher for this scenario. (Documentation)
This seems to work:
expect(response).to render_with_layout('my_layout')
it produces appropriate failure messages like:
Expected to render with the "calendar_layout" layout, but rendered with "application", "application"
Tested with rails 4.2, rspec 3.3 and shoulda-matchers 2.8.0
Edit: shoulda-matchers provides this method. Shoulda::Matchers::ActionController::RenderWithLayoutMatcher
Here's a version of dmcnally's code that allows no arguments to be passed, making "should use_layout" and "should_not use_layout" work (to assert that the controller is using any layout, or no layout, respectively - of which I would expect only the second to be useful as you should be more specific if it is using a layout):
class UseLayout
def initialize(expected = nil)
if expected.nil?
#expected = nil
else
#expected = 'layouts/' + expected
end
end
def matches?(controller)
#actual = controller.layout
##actual.equal?(#expected)
if #expected.nil?
#actual
else
#actual == #expected
end
end
def failure_message
if #expected.nil?
return 'use_layout expected a layout to be used, but none was', 'any', #actual
else
return "use_layout expected #{#expected.inspect}, got #{#actual.inspect}", #expected, #actual
end
end
def negative_failure_message
if #expected.nil?
return "use_layout expected no layout to be used, but #{#actual.inspect} found", 'any', #actual
else
return "use_layout expected #{#expected.inspect} not to equal #{#actual.inspect}", #expected, #actual
end
end
end
def use_layout(expected = nil)
UseLayout.new(expected)
end

Resources