How Can I Refactor These Assertions To Avoid Duplication - ruby-on-rails

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

Related

expected `secure_user.birth_date` to have changed from "1990-01-01" to "2000-01-01", but did not change

Here is my rspec test code
require 'rails_helper'
RSpec.describe Users::PaymentController, type: :controller do
let(:user) { create(:user) }
let(:secure_user) { create(:secure_user, user_id: user.id, email: 'abc123#mail.com', birth_date: '1990-01-01', gender: 'female', nation: 'jp', prefecture: 'Tokyo-to', municipality: 'Shibuya-ku') }
describe 'POST #create' do
params = {
birth_date: '2000-01-01',
gender: 'male',
prefecture: 'Osaka-fu',
municipality: 'Osaka-shi'
}
it "changes secure_user's attributes" do
expect do
post :create, params: params
end.to change{ secure_user.birth_date }.from('1990-01-01').to('2000-01-01')
end
end
end
and here is the related part from Users::PaymentController
module Users
class PaymentController < Users::BaseController
def create
current_user.secure_user.update(user_personal_info_params)
......
......
end
private
def user_personal_info_params
params.permit(:birth_date, :gender, :nation, :prefecture, :municipality)
end
end
end
The logic is: add new info to current_user.secure_user while payment is created.
But the test fails and I don't know the reason.
1) Users::PaymentController POST #create changes secure_user's attributes
Failure/Error:
expect do
post :create, params: user_info
binding.pry
end.to change{ secure_user.birth_date }.from('1990-01-01').to('2000-01-01')
expected `secure_user.birth_date` to have changed from "1990-01-01" to "2000-01-01", but did not change
I am not very familiar with RSpec (and so does English), much appreciated if anybody could help me solve this problem.
Try to use .reload
...
end.to change{ secure_user.reload.birth_date }.from('1990-01-01').to('2000-01-01')
The object doesn't get updated inside the test, you have to reload it, to have the up to date attributes.

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 are my tests telling me "param is missing or the value is empty:"?

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

Rspec having difficulty testing #create with build controller method

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

What is the proper way to test 'create' controller actions?

I am using Ruby on Rails 3.2.2, Rspec 2.9.0 and RspecRails 2.9.0. I would like to test the create controller action but I don't know how to make that the "right"/"proper" way. I "scaffolded" model, controller, view, ... files, so in those files I have the common code generated by Ruby on Rails generators; in my spec file I have:
it "assigns #article" do
new_article = FactoryGirl.build(:article)
Article.should_receive(:new).and_return(new_article)
post :create
assigns[:article].should eq(new_article)
end
Maybe, (note: the above code is almost the same as that I use to test the new controller action) a better way to test create controller actions would be to pass some attribute value during the post :create action instead of proceed as I make above, but I don't know how to make that and if it is the "right"/"proper" way to make things.
So, what is the proper way to test 'create' controller actions?
I'm doing it this way:
describe "#create" do
before { post :create, { "my_model"=> { "name"=>"name" } } }
specify("should created one my_model") { change{ MyModel.count }.from(0).to(1) }
end
Aaron Sumner who recently wrote the book Everyday Rails Testing with RSpec have an article at his blog. Where he describes it like this:
describe "POST create" do
context "with valid attributes" do
it "creates a new contact" do
expect{
post :create, contact: Factory.attributes_for(:contact)
}.to change(Contact,:count).by(1)
end
it "redirects to the new contact" do
post :create, contact: Factory.attributes_for(:contact)
response.should redirect_to Contact.last
end
end
context "with invalid attributes" do
it "does not save the new contact" do
expect{
post :create, contact: Factory.attributes_for(:invalid_contact)
}.to_not change(Contact,:count)
end
it "re-renders the new method" do
post :create, contact: Factory.attributes_for(:invalid_contact)
response.should render_template :new
end
end
end
How about:
it "creates article" do
article_params = FactoryGirl.attributes_for(:article)
expect { post :create, :article => article_params }.to change(Article, :count).by(1)
end

Resources