I have a controller and tests it through rspec:
describe "GET 'index'" do
subject { get :index }
it { expect(subject).to render_template(:index) }
My controller generates instance variables passed to views, smth. like that:
#specifications = current_user.specifications
How can I test that controller pass instance variables correct?
Something like that:
it { expect(subject).assign(:contractors).to match_array(my_array) }
You can use controller helper test method
describe TetsController do
let(:user) { build_stubbed :user }
before do
controller.stub authenticate_user!: true,
current_user: user
end
describe 'GET index' do
let(:plans) { double :plans }
before do
expect(Plan).to receive(:all).and_return(plans)
end
it 'response success' do
get :index
expect(response).to be_success
end
it 'assign plans' do
get :index
expect(assigns(:plans)).to eq plans
end
end
end
Small example. controller has instance variable #plans. It's accessed as assigns(:plans)
Related
I have the following spec fragment:
it 'should create company and user' do
company_iv = assigns(:company)
user_iv = assigns(:user)
expect(subject).to receive(:create_timeline_event).with(company_iv, user_iv)
expect { post :create, params }.to change { User.count }.by(1).and change { Company.count }.by(1)
and traditionally use the receive syntax to test calling a method. I normally call it before the call to post in the above fragment. How would I access the instance variable of the user and the company for this spec?
Looks like you're trying to jam a few different tests into a single it statement. Here's how I would approach this:
it 'creates company and user' do
expect { post :create, params }
.to change { User.count }.by(1)
.and change { Company.count }.by(1)
end
it 'assigns instance variables' do
post :create, params
expect(assigns(:company)).to eq(Company.last)
expect(assigns(:user)).to eq(User.last)
end
it 'calls create_timeline_event with newly created company and user' do
allow(some_object).to receive(:create_timeline_event)
post :create, params
expect(some_object)
.to have_received(:create_timeline_event)
.with(Company.last, User.last)
end
Note that these tests are going to be slow because they hit the database. Another approach to this is to use mocks. That would look something like this:
let(:params) { ... }
let(:company) { instance_double(Company) }
let(:user) { instance_double(User) }
before do
allow(Company).to receive(:create).and_return(company)
allow(User).to receive(:create).and_return(user)
allow(some_object).to receive(:create_timeline_event)
post :create, params
end
it 'creates company and user' do
expect(Company).to have_received(:create).with(company_params)
expect(User).to have_received(:create).with(user_params)
end
it 'assigns instance variables' do
expect(assigns(:company)).to eq(company)
expect(assigns(:user)).to eq(user)
end
it 'calls create_timeline_event with newly created company and user' do
expect(some_object)
.to have_received(:create_timeline_event)
.with(company, user)
end
These tests do not hit the database at all, meaning that they'll execute much faster.
I'm new in testing and learning Rspec, and I can't git it working.
(I have read the book Effective testing with Rspec3, and many tutorials ...also pluralsight.com)
The situation is very simple. In a Companies controller I want to test de Create method, the company model belongs_to user, and is this the code:
I think the problem is when execute
in test: expect(Company).to receive(:new).with(company_params)
or in controller: #company.user=helpers.user
Controller:
class CompaniesController < SessionsController
def create
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
redirect_to companies_path
else
render :edit
end
end
and Rspec:
RSpec.describe CompaniesController, type: :controller do
let(:user) { instance_double(User) }
before do
allow_any_instance_of(ApplicationHelper).to receive(:user){user}
allow(controller).to receive(:authorize){true}
end
describe 'Authenticated user with companies' do
let(:company_params) { {company:{name:"Albert",domain:"www.albert.com"}} }
let(:company) { instance_double(Company) }
before do
allow(Company).to receive(:new){company}
end
describe 'POST #create' do
context "with valid data" do
before { allow(company).to receive(:save){true} }
it "redirects to companies_path" do
expect(Company).to receive(:new).with(company_params)
expect(company).to receive(:user=).with(user)
post :create, params:{company: company_params}
expect(response).to redirect_to(companies_path)
end
My intention is very simple: Use instance_double to mock (or stub) #company, and Company.new, using instance double...to test the create action, and simulate the "save()" returning true...etc
I do not know if I explain myself very well, but given the create action of controlloer , how to test using mocks ans stubs, instance_double?
Thanks
First of all let me explain what we need to test here
def create
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
redirect_to companies_path
else
render :edit
end
end
We are testing create action of a controller. First let us see what this action does? It's just takes comapany_params as input and create a company record in database.
Testing also goes like the same, we need to just pass the input that action required, and need to check whether it's creating record in database or not.
RSpec.describe CompaniesController, type: :controller do
let(:user) { instance_double(User) }
before do
# all your authentication stubing goes here
allow_any_instance_of(ApplicationHelper).to receive(:user){user}
allow(controller).to receive(:authorize){true}
end
describe 'POST#create' do
context 'with valid attributes' do
before do
post :create, { company:{ name:"Albert", domain:"www.albert.com"} }
end
it 'responds with success' do
expect(response.status).to eq(302)
end
it 'creates company' do
company = Company.find_by(name: "Albert")
expect(assigns(:company)).to eq(company)
expect(response).to redirect_to(companies_path())
end
end
context 'with invalid attributes' do
before do
post :create, { company:{ name:"", domain:""} }
end
it 'renders new template' do
expect(response).to render_template(:edit)
end
end
end
end
No need to sub anything here. As per my knowledge, Only when we use any lib classes / background jobs / third party libraries code inside action then we need to stub those code. Because for all those, we will write specs separately. So no need to test again here that's why we'll do stubing.
Thanks to Narsimha Reddy, I have better ideas about how to test.
Eventhough, if I want to stub
#company=Company.new(company_params)
#company.user=helpers.user
if #company.save()
For testing only de create's response , the solution was in a good use of parameters, and allowing allow(company).to receive(:user=) for the belongs_to association
let(:company_params) {{company:{name:"Albert",domain:"www.albert.com"}}}
let(:ac_company_params) {ActionController::Parameters.new(company_params).require(:company).permit!}
let(:company) { instance_double(Company) }
before do
allow(Company).to receive(:new){company}
allow(company).to receive(:user=)
allow(company).to receive(:save){true}
end
it "redirects to companies_path" do
expect(Company).to receive(:new).with(ac_company_params)
expect(company).to receive(:user=).with(user)
post :create, params: company_params
expect(response).to redirect_to(companies_path)
end
How can I double #user var, if I do not double it, the test will throw error of
call update_attributes of nil:NilClass
class TestController
def foo!
#user = current_user
#user.update_attribute(user_params)
end
end
RSpec.describe TestController, type: :controller do
describe "#foo" do
it "should be passed" do
#specific_user = FactoryGirl.create(:user)
allow_any_instance_of(TestController).to receive(:foo!).and_return(true)
allow_any_instance_of(TestController).to receive(#user).and_return(#specific_user)
end
end
end
The problem with your test is that it doesn't test anything. The controller test should make a request to test the method get :foo!
About stubbing, in your case current_user method can be stubbed instead:
RSpec.describe TestController, type: :controller do
describe "#foo" do
it "should be passed" do
#specific_user = FactoryGirl.create(:user)
allow(controller).to receive(:foo!).and_return(true)
allow(controller).to receive(:current_user).and_return(#specific_user)
end
end
end
And yeah, in the controller test the controller instance can be accessed by calling controller method.
What also allows to set an instance variable in this controller:
RSpec.describe TestController, type: :controller do
describe "#foo" do
it "should be passed" do
#specific_user = FactoryGirl.create(:user)
allow(controller).to receive(:foo!).and_return(true)
controller.instance_variable_set(:#user, #specific_user)
end
end
end
My controller and my test file are bellow.
controllers/reports_controller.rb:
def index
#reports = Report.all
end
specs/controllers/reports_controller_spec.rb:
RSpec.describe ReportsController, type: :controller do
let(:test_report) {
2.times.map {
create(:report, student: create(:student), report_options_attributes: [
{option: create(:option), note: "ole" }
])
}
}
describe "GET #index" do
before(:each) do
get :index
end
it "should be success" do
expect(response).to be_success
end
it "should render index template" do
expect(response).to render_template(:index)
end
it "should load all reports" do
expect(assigns(:report)).to match_array test_report
end
end
The last test is not working, but it should work. What is wrong with it?
index test is empty..you need to assert something to pass.
can you add.. assert_response :success in index function.
Your var is different from the controller. Use reports instead of report like this:
it "should load all reports" do
expect(assigns(:reports)).to match_array test_report
end
It should work.
This is my Spec file:
require 'rails_helper'
RSpec.describe Programmes::ReportsController, :type => :controller do
let!(:programme) { create(:programme) }
context 'authenticated user' do
describe 'GET index' do
it 'responds with a 200 OK status code' do
get :index, params: { id: programme.id }
expect(response).to have_http_status(:success)
end
end
end
end
This is my Factory;
FactoryGirl.define do
factory :programme do
name { Faker::Lorem.word }
description { Faker::Lorem.sentence(3) }
end
end
This is my Controller;
class Programmes::ReportsController < ApplicationController
def index
end
def create
end
end
I can't seem to get this spec to pass. The route works fine in the browser; eg
http://localhost:3000/programmes/{:id}/reports
The error I have is:
Failures:
1) Programmes::ReportsController authenticated user GET index responds with a 200 OK status code
Failure/Error: let!(:programme) { create(:programme) }
NoMethodError:
undefined method `create' for #<RSpec::ExampleGroups::ProgrammesReportsController::AuthenticatedUser::GETIndex:0x007fac78b1b440>
# /Users/mike/.rvm/gems/ruby-2.2.3/gems/actionpack-5.0.0/lib/action_dispatch/testing/assertions/routing.rb:172:in `method_missing'
I am quite new to Ruby (and Rails). I don't think the Programme object is being created in FactoryGirl - but I don't really know how to find out if that's the case
Did you require 'factory_girl' in spec_helper?