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.
Related
I'm studying rails and rspec.
And I made rspec unit test (request test) on rails application.
But after searching on google, I'm wonder if my job is on right way.
Can my code be a "Unit test by function(not a method, web site's feature ex)create, show, delete..) of rails application" ?
this is my code with request test.
require 'rails_helper'
RSpec.describe 'Users', type: :request do
let!(:users) { create_list(:user, 10) }
let(:user_id) { users.first.id }
let(:user) { create(:user) }
def send_request_to_store_user(name, mailaddress)
post '/users', params: {
user: {
name: users.first.name,
mailaddress: users.first.mailaddress
}
}
end
def http_status_success_and_body_element_check(body_element)
expect(response).to have_http_status(:success)
expect(response.body).to include(body_element)
end
describe 'GET' do
context 'Get /users test' do
it 'test user list page' do
get '/users'
http_status_success_and_body_element_check('User List')
end
end
context 'Get /users/create test' do
it 'test user create page' do
get '/users/create'
http_status_success_and_body_element_check('create user')
end
end
context 'Get /users/:id/edit' do
it 'test user edit page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('edit user')
end
end
context 'Get /users/:id' do
it 'test user show page' do
get "/users/#{user_id}"
http_status_success_and_body_element_check('show user')
end
end
end
describe 'POST' do
context 'test store new user' do
it 'test create new user' do
send_request_to_store_user(user.name, user.mailaddress)
expect do
create(:user)
end.to change { User.count }.from(User.count).to(User.count + 1)
end
it 'test redirect after create' do
send_request_to_store_user(user.name, user.mailaddress)
expect(response).to have_http_status(302)
end
end
end
describe 'DELETE' do
it 'test delete user' do
expect do
delete "/users/#{user_id}"
end.to change { User.count }.from(User.count).to(User.count - 1)
expect(response).to have_http_status(302)
end
end
describe 'PUT' do
context 'user update' do
it 'test user information update' do
old_name = users.first.name
new_name = 'new_name'
expect do
put "/users/#{user_id}", params: {
user: {
name: new_name
}
}
end.to change { users.first.reload.name }.from(old_name).to(new_name)
expect(response).to have_http_status(:redirect)
end
end
end
end
this is my code with test on model
require 'rails_helper'
RSpec.describe User, type: :model do
it 'user must have name and mailaddress' do
user = create(:user)
expect(user).to be_valid
expect(user.name).not_to be_nil
expect(user.mailaddress).not_to be_nil
end
it 'mailaddress must include #' do
# user = FactoryBot.create(:user)
# If rails_helper.rb has config.include FactoryBot::Syntax::Methods,
# Can use shortcut. Don't have to FactoryBot.create
user = create(:user)
# Test pass if email match with regexp
expect(user.mailaddress).to match(/\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/)
end
end
I don't think these tests are valuable (meaningful).
Here's my reasoning:
What are these tests telling you? That the Rails router is working? That the controller is responding with the right action? Neither of these are your responsibility to test. Rails has that covered.
If you want to know "does the index page render?" and "can I CRUD a user?" then write system tests with Capybara that simulate the whole flow. That way you are testing the real-world interaction with your whole system.
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
Using Rails 4.2, rspec 2.14, rspec-rails 2.14, faker and factory-girls-rails gems
I have a model called Appointment that I'm running some tests on and everything passes except for the #create under the controller spec.
The error message I get is:
Failure/Error: post :create, FactoryGirl.attributes_for(:appointment)
ActionController::ParameterMissing:
param is missing or the value is empty: appointment
The Appointment model validates the presence of an association to an object called Service.
Here is my factory for appointment.rb:
require 'faker'
FactoryGirl.define do
factory :appointment do |f|
f.service {FactoryGirl.create(:service)}
f.appointment_time { Faker::Time.between(DateTime.now - 1, DateTime.now) }
end
end
Here is my appointment_spec.rb:
require 'spec_helper'
describe Appointment do
it "has a valid factory" do
FactoryGirl.create(:appointment).should be_valid
end
it "is invalid if it does not have a Service association" do
FactoryGirl.build(
:appointment, service: nil).should_not be_valid
end
end
I've been following the instructions listed here for making my Controller Spec. I've also found a lot of stackoverflow posts that say to do the same thing, yet I still get the same error.
Here are the tests not passing from my appointment_controller_spec.rb
describe AppointmentsController do
#other controller action code...
describe "POST #create" do
context "with valid attributes" do
it "saves the new appointment in the database" do
expect {
post :create, FactoryGirl.attributes_for(:appointment)
}.to change(Appointment, :count).by(1)
end
it "redirects to show page" do
post :create, FactoryGirl.attributes_for(:appointment)
response.should redirect_to Appointment.last
end
end
end
I'm at a loss and hoping some one can offer some insight.
EDIT:
As some of you had recommended, I changed the controller spec. This is actually what I had before I changed my code to what you see above:
it "saves the new appointment in the database" do
expect {
post :create, appointment: FactoryGirl.attributes_for(:appointment)
}.to change(Appointment, :count).by(1)
end
The reason I changed this is because when I had this my original error message was:
Failure/Error: expect {
count should have been changed by 1, but was changed by 0
Sorry for the confusion.
I believe you just need your AppointmentsController spec to read as follows:
describe AppointmentsController do
#other controller action code...
describe "POST #create" do
context "with valid attributes" do
it "saves the new appointment in the database" do
expect {
post :create, appointment: FactoryGirl.attributes_for(:appointment)
}.to change(Appointment, :count).by(1)
end
it "redirects to show page" do
post :create, appointment: FactoryGirl.attributes_for(:appointment)
response.should redirect_to Appointment.last
end
end
end
Adding appointment: before you supply the attributes via FactoryGirl in the post call.
Are you using strong_params in your controller? It looks like you are looking for an appointment param, but you are just getting a hash of the attributes.
Try this:
it "saves the new appointment in the database" do
expect {
post :create, appointment: FactoryGirl.attributes_for(:appointment)
}.to change(Appointment, :count).by(1)
end
So if your #create controller method is simply:
#testimonial = Testimonial.new(testimonial_params)
And you test it in your specs like so:
testimonials_controller_spec.rb
describe "POST #create" do
context "with VALID attributes" do
it "creates new testimonial" do
expect {
post :create, testimonial: FactoryGirl.attributes_for(:testimonial)
}.to change(Testimonial, :count).by(1)
end
end
end
It works fine. The code:
post :create, testimonial: FactoryGirl.attributes_for(:testimonial)
is correct.
However, in my TestimonialsController, my create method is actually:
#testimonial = current_user.testimonials.build(testimonial_params)
My rspec method doesn't work with this. What should I use instead of:
post :create, testimonial: FactoryGirl.attributes_for(:testimonial)
?
Sign in the user before calling the controller action. Please find the following:
#testimonials_controller_spec.rb
require 'rails_helper'
describe TestimonialsController, type: :controller do
let(:user) do
FactoryGirl.create :user
end
before do
sign_in user
end
describe "POST #create" do
context "with VALID attributes" do
it "creates new testimonial" do
expect {
post :create, testimonial: FactoryGirl.attributes_for(:testimonial)
}.to change(Testimonial, :count).by(1)
end
end
end
end
Build doesn't save/persist the record to the database. Why not make it:
#testimonial = current_user.testimonials.new(testimonial_params)
#testimonial.save
Is there a way to refactor this to avoid the repetition of:
post :create, user: attributes_for(:user)
Given that the first assertion needs to wrap it in an expect block, I don't see a way of moving it to a before block. Obviously I could wrap the last two assertions in a context or describe block with its own before block, but this doesn't feel right.
context 'with valid attributes' do
it 'should create a new User and save it to the database' do
expect {
post :create, user: attributes_for(:user)
}.to change(User, :count).by(1)
end
it {
post :create, user: attributes_for(:user)
should redirect_to(user_path(assigns[:user]))
}
it {
post :create, user: attributes_for(:user)
should set_the_flash[:notice]
}
end
You can put your "action" in a method, as follows:
context 'with valid attributes' do
it 'should create a new User and save it to the database' do
expect {
do_action
}.to change(User, :count).by(1)
end
it {
do_action
should redirect_to(user_path(assigns[:user]))
}
it {
do_action
should set_the_flash[:notice]
}
def do_action
post :create, user: attributes_for(:user)
end
end
Something like this (not make sure that it works)
context 'with valid attributes' do
before { post :create, user: attributes_for(:user) }
it 'should create a new User and save it to the database' do
expect(User.count).to eq 1
end
it { should redirect_to(user_path(assigns[:user])) }
it { should set_the_flash[:notice] }
end