Using FactoryGirl for a controller test - ruby-on-rails

I want to test my Users#show controller. How can I use FactoryGirl to create test data to be passed into my controller?
In "spec/controllers/users_controller_spec.rb":
describe UsersController do
describe "GET #show" do
it "assigns the requested user to #user" do
user = Factory(:user) # How do I do this using FactoryGirl?
get :show, id: user
assigns(:user).should eq(user)
end
end
end
In "spec/factories/users.rb"
require 'faker'
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
end
# Replace email with nil. Apparently all other attributes defer to the
# original :user factory.
factory :invalid_user do
email nil
end
end

To wrap things up:
Use create(:user) or build(:user) as shown in latest docs instead of Factory(:user).
build(:user) does not save the object to database therefore you will probably have to stub controller's queries. It's faster though.
To pass the id of not persisted user you'll have to do get :show, id: user.id instead of get :show, id: user

Related

Testing with Rspec3 controllers with belongs_to association using instance_double

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

rspec ActiveRecord::RecordNotFound: Couldn't find Proposal with 'id'=

I run test, display error.
Failures:
1) ContractsController POST #create with valid attributes redirects to payment page
Failure/Error: #proposal = Proposal.find(params[:proposal_id])
ActiveRecord::RecordNotFound:
Couldn't find Proposal with 'id'=
require 'rails_helper'
describe ContractsController do
login_client
describe 'POST #create' do
let(:proposal) { create(:proposal) }
let(:contract) { create(:contract) }
context 'with valid attributes' do
it 'redirects to payment page' do
post :create, contract: attributes_for(:contract)
expect(response).to redirect_to payment_new_path
end
end
end
end
factory girls:
FactoryGirl.define do
factory :contract do
sequence(:title) { |n| "translation#{n}" }
amount 150
additional_information 'X' * 500
due_date { 21.days.from_now }
proposal
client
contractor
end
end
FactoryGirl.define do
factory :proposal do
description text
amount 150
project
user
end
end
I'm sure you're getting this error because of the use of FactoryGirl#attributes_for. Why? When you use the attributes_for method, it returns a non-persisted hash attribute for the resource. The thing about attributes_for however is that it doesn't honor association, which makes sense(in order to keep FactoryGirl ORM agnostic). A suggested way around this is to use or define a custom strategy:
build(:contract).attributes
Find more useful references here

Why does my test return a nil class error on an attribute it shouldn't?

I am trying to write a test for my InvitationsController#Create.
This is a POST http action.
Basically what should happen is, once the post#create is first executed, the first thing that needs to do is we need to check to see if a User exists in the system for the email passed in via params[:email] on the Post request.
I am having a hard time wrapping my head around how I do this.
I will refactor later, but first I want to get the test functionality working.
This is what I have:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
This is the error I get:
1) Users::InvitationsController POST #create when invited user IS an existing user correctly finds User record of invited user
Failure/Error: post :create, { email: #users.first[:email] }
NoMethodError:
undefined method `name' for nil:NilClass
##myapp/gems/devise-3.2.4/app/controllers/devise_controller.rb:22:in 'resource_name'
# #myapp/gems/devise_invitable-1.3.6/lib/devise_invitable/controllers/helpers.rb:18:in 'authenticate_inviter!'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:67:in 'current_inviter'
# #myapp/gems/devise_invitable-1.3.6/app/controllers/devise/invitations_controller.rb:71:in 'has_invitations_left?'
I am using FactoryGirl and it works perfectly, in the sense that it returns valid data for all the data-types. The issue here is how do I get RSpec to actually test for the functionality I need.
Edit 1
Added my :user factory:
FactoryGirl.define do
factory :user do
association :family_tree
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password "password123"
password_confirmation "password123"
bio { Faker::Lorem.paragraph }
invitation_relation { Faker::Lorem.word }
# required if the Devise Confirmable module is used
confirmed_at Time.now
gender 1
end
end
It seems you're using Devise which require you to be logged in before going to the next step. On your error, Devise cannot get the same of your inviter because he's not logged.
Your test should be like this:
describe 'POST #create' do
context 'when invited user IS an existing user' do
before :each do
#users = [
attributes_for(:user),
attributes_for(:user),
attributes_for(:user)
]
#another_user = FactoryGirl.create(:user_for_login)
sign_in #another_user
end
it 'correctly finds User record of invited user' do
post :create, { email: #users.first[:email] }
expect(response).to include(#users.first[:email])
end
end
end
Example for FactoryGirl model for Devise
factory :user_for_login, class: User do |u|
u.email 'admin#myawesomeapp.com'
u.password 'password'
u.password_confirmation 'password'
u.name "MyName"
end
Of course, you need to add as much data as your validators want.. Basically for Devise you need email, password and password_confirmation. In you case, it seems you also need name.

Rails Controller testing

I am doing the thoughtbot intro to testing program. Im not sure how to test for what they want.
Below is my test.
require "rails_helper"
describe PeopleController do
describe "#create" do
context "when person is valid" do
it "redirects to #show" do
post :create, FactoryGirl.build_stubbed(:person)
expect(response).to redirect_to(show_people_path)
end
end
context "when person is invalid" do
it "redirects to #new" do
pending "create this test"
end
end
end
end
I am of course using factory girl. I have tried several methods. I really don't know hoe to test this controller.
Any insights would be great.
I would create an 'invalid' person using the FactoryGirl, and send it as a parameter to the post :create.
To create an invalid person record, why don't you use nested factories in FactoryGirl? Depending on the validation in your model, you can simply do something like:
spec/factories/person.rb
FactoryGirl.define do
factory :person do
...
factory :invalid_person do
...
email nil
...
end
end
end
in your test
context "when person is invalid" do
it "redirects to #new" do
post :create, FactoryGirl.build_stubbed(:invalid_person)
expect(response).to redirect_to action: :new
end
end

Testing a rails controller method that is designed to change data of a variable

I have the following code in the controller:
# guest to user sign up view. Method that prepares a guest to become a user by emptying it's generic
#e-mail address.
def guest_signup
if !current_user.guest
redirect_to root_url
end
#user = current_user
#user.email = ""
end
This controller just makes sure that the outcome (a form) doesn't have a generic e-mail address in an input field that the user gets assigned when he is using the application as guest.
I am trying to write an rspec test for it and I have no idea how to properly do it... I know this may sound like development-driven testing rather than the opposite but I need an idea.
Currently I have this that doesn't work:
require 'spec_helper'
describe UsersController do
describe "Guest Signup" do
it "should prepare guest with random e-mail user for signup form, emptying the e-mail" do
current_user = User.create(:email => "guest_#{Time.now.to_i}#{rand(99)}#example.com", :password => "#{Time.now.to_i}#{rand(99999999)}", :guest => true)
get :guest_signup, :user => #user.id
expect(#user.email).to eq ('')
end
end
end
How is #user assigned here? Presumably after the guest_signup method is called. Since #user is referenced in the call to guest_signup, you have an order of operations problem here.
Maybe you should be calling:
get :guest_signup, :user => current_user.id
describe UsersController do
describe 'Guest Signup' do
let(:user) { mock.as_null_object }
before(:each) { controller.stub(:current_user) { user } }
context 'when guest does not exist' do
before(:each) { user.stub(:guest) { false } }
it 'redirects to root path' do
get :guest_signup
response.should redirect_to root_path
end
end
context 'when guest exists' do
before(:each) { user.stub(:guest) { true } }
it 'should prepare guest with random e-mail user for signup form, emptying the e-mail' do
get :guest_signup
assigns(#user).should == user
assigns(#email).should be_empty
end
end
end
end

Resources