RSpec passing object as a param in a controller_spec - ruby-on-rails

I have a users_controller_spec.rb with this:
describe "POST create" do
describe "with valid params" do
let(:user) { create(:user) }
it "assigns a newly created user as #user" do
post :create, user: user
assigns(:user).should be_a(User)
assigns(:user).should be_persisted
end
end
...
end
Debuggin I found that the controller receive the next params
(rdb:1) pp params
{"user"=>"1", "controller"=>"users", "action"=>"create"}
Why "user" => "1" ?, why is not passing the user object properly ?

post :create expects attributes for the user model that it will use to create a user record. you are seeing "user" => "1" because it is passing in the id of the user you created into the :user parameter.
You dont want to create a user record to test the create action. You want to create a hash of attributes for the create action to create the record.
You could write something like this (assuming this would pass your model validations):
user_attributes = { :email => "something#example.com", :username => "something" }
post :create, user: user_attributes

Related

How to access instance variables to test `receive` in a spec?

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.

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.

Functional Testing for Customer Parameter Validation - Rspec / Capybara

I Added customer validation in my controller like
def customer_create
if params[:api_key].present?
## Create Customer
else
## Render Error Message in Json format
end
end
How can I write Rspec Testing for above method?
Thanks In Advance
presumably customer_create is called from your controller's create action?
So it might be sufficient to simply test that action.
describe CustomersController do
it "creates customer if :api_key is present` do
post :create, api_key: "present key", customer_attributes
expect(Customer.count).to eq 1
end
it "does not create customer if :api_key is absent` do
json_error = {
key1: 'value1',
key2: 'value2'
}.to_json
post :create, customer_attributes
expect(response.body).to eq json_error
end
end
You can test the method directly, if you set up the params.
describe CustomersController do
it "creates customer if :api_key is present' do
controller.params[:api_key] = 'present key'
controller.params.merge!(customer_attributes)
controller.customer_create
expect(Customer.count).to eq 1
end
end
Both examples assume customer attributes hash is stored in a variable customer_attributes

Using FactoryGirl for a controller test

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

Rails Rspec No Route Error When Route Obviously Exists

I have been writing some rspec tests, and right now I want to verify a redirect after user creation. Many other tests work, including creating users.
However, the last of the following tests fail:
describe "success" do
before(:each) do
#attr = { :username => "woweee123",
:email => "email#mail.com",
:password => "password123",
:password_confirmation => "password123"}
end
it "should create the user" do
lambda do
post :create, :user => #attr
end.should change(User, :count)
end
it "should redirect to a user show page after creation" do
lambda do
post :create, :user => #attr
end.should redirect_to(user_path(assigns(:user)))
end
end
The failed test is: No route matches {:action=>"show", :controller=>"users"
But, when I create a user manually (on localhost) the redirect obviously works. Why is this failing?
I would check if
assigns(#user).new_record?
is not true anymore after the post. It seems like it doesn't have an id in your test. Could some validation have failed?
For Those interested the answer is that I had to change the test so that it looks like:
it "should redirect to a user show page after creation" do
post :create, :user => #attr
response.should redirect_to(user_path(assigns(:user)))
end
This works.

Resources