I have a Transaction model, in which I have the following scope :
scope :ownership, -> { where property: true }
I made some tests of the controller (thanks to M. Hartl). There they are :
require 'spec_helper'
describe TransactionsController do
let(:user) { FactoryGirl.create(:user) }
let(:product) { FactoryGirl.create(:givable_product) }
before { be_signed_in_as user }
describe "Ownerships" do
describe "creating an ownership with Ajax" do
it "should increment the Ownership count" do
expect do
xhr :post, :create, transaction: { property: true, user_id: user.id, product_id: product.id }
end.to change(Transaction.ownership, :count).by(1)
end
it "should respond with success" do
xhr :post, :create, transaction: { property: true, user_id: user.id, product_id: product.id }
expect(response).to be_success
end
end
describe "destroying an ownership with Ajax" do
let(:ownership) { user.transactions.ownership.create(product_id: product.id, user_id: user.id) }
it "should decrement the Ownership count" do
expect do
xhr :delete, :destroy, id: ownership.id
end.to change(Transaction.ownership, :count).by(-1)
end
it "should respond with success" do
xhr :delete, :destroy, id: ownership.id
expect(response).to be_success
end
end
end
end
And there is the destroy method of my Transaction controller :
def destroy
#transaction = Transaction.find(params[:id])
#property = #transaction.property
#product = #transaction.product
#transaction.destroy
respond_to do |format|
format.html { redirect_to #product }
format.js
end
end
But when I run the tests, one of them fails, and I don't understand how or why :
1) TransactionsController Ownerships destroying an ownership with Ajax should decrement the Ownership count
Failure/Error: expect do
count should have been changed by -1, but was changed by 0
# ./spec/controllers/transactions_controller_spec.rb:31:in `block (4 levels) in <top (required)>'
Can you help me about it ?
You can use 'let!'.
About 'let' and 'let!': https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let
From the RSpec documentation there's a difference between let and let! (see here);
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first
time the method it defines is invoked. You can use let! to force the
method's invocation before each example.
In your destroy method use let!(:ownership) so that the ownership object is not cached after it is destroyed.
Related
I'm biginer.
I studied Rspec.
I made an implementation that allowed me to do good on my posts.
But on the browser I do the expected move, but the test does not pass.
The destroy action goes through the test, but the create action does not pass the test.
My error is
Failure/Error: expect { post :create, format: :js, params: { post_id: post1.id, id: like.id } }.to change(Like, :count).by(1)
expected #count to have changed by 1, but was changed by 0
My code is
require 'rails_helper'
RSpec.describe LikesController, type: :controller do
let!(:user) { create(:user) }
let!(:post1) { create(:post, user: user) }
let!(:like) { create(:like, user_id: user.id, post_id: post1.id) }
describe "#create" do
before do
sign_in user
end
it "response Ajex" do
post :create, format: :js, params: { post_id: post1.id, id: like.id }
expect(response.content_type).to eq 'text/javascript'
end
it "success like function" do
expect { post :create, format: :js, params: { post_id: post1.id, id: like.id } }.to change(Like, :count).by(1)
end
end
describe "#destroy" do
before do
sign_in user
end
it "response Ajex" do
delete :destroy, format: :js, params: { post_id: post1.id, user_id: user.id, id: like.id }
expect(response.content_type).to eq 'text/javascript'
end
it "delete like function" do
expect { delete :destroy, format: :js, params: { post_id: post1.id, user_id: user.id, id: like.id } }.to change(Like, :count).by(-1)
end
end
end
likes_controller.rb
class LikesController < ApplicationController
def create
#like =
current_user.likes.find_or_create_by(post_id:params[:post_id])
#likes = Like.where(post_id: params[:post_id])
#post = Post.find(params[:post_id])
end
def destroy
like = current_user.likes.find_by(post_id: params[:post_id])
like.destroy
#likes = Like.where(post_id: params[:post_id])
#post = Post.find(params[:post_id])
end
end
I cannot solove this problem.
Please teach me a hint.
You've got an error in your code somewhere, most likely, which is why the Like count fails to increment. First, I'd try and figure out why it isn't incrementing. Since you asked for a hint, here's one way you can split out the "success like function" block:
context "valid" do
before do
post :create, format: :js, params: { post_id: post1.id, id: like.id }
end
it "success" do
# You can inject a binding.pry here if needed
expect(response.status).to eq(200)
end
it "response" do
# You can inject a `binding.pry` here if needed
# You can also inspect the `response.body` with puts if needed
expect(JSON.parse(response.body)).to include(
# You would modify this to match the shape of your response
post: a_hash_including(
like: like.id
)
)
end
end
You'll want to install pry-rails and pry-byebug gems (for inspecting).
The reason behind splitting them up is it makes it easier to determine the issue (you can have a valid response code but not the expected result, for example). This comes with some caveats (it will make for slower tests) but in this example it will make it easier to determine why your post is failing.
The snippet above should help you debug the error; once you fix it you can revert back to your previous method of checking.
I'm trying to fix some tests that I have written in my comments controller. As of now, with my current tests I get this error:
Failure/Error: #outlet = Outlet.find(params[:comment][:outlet_id])
ActiveRecord::RecordNotFound:
Couldn't find Outlet with 'id'=
Here is an example of some of the tests
describe '#create' do
context 'with valid attributes' do
before :each do
#outlet = FactoryGirl.create(:outlet)
#user = FactoryGirl.create(:user)
#comment_params = FactoryGirl.attributes_for(:comment)
end
let(:create) { post :create, params: { outlet_id: #outlet.id, user_id: #user.id, comment: #comment_params } }
it "creates new comment" do
expect { create }.to change { Comment.count }.by(1)
end
it "increases the post comment count by 1" do
expect { create }.to change { #outlet.comments.count }.by(1)
end
it "increases user comment count by 1" do
expect { create }.to change { #user.comments.count }.by(1)
end
end
end
I'm pretty sure this is happening because of my create statement in my tests
let(:create) { post :create, params: { outlet_id: #outlet.id, user_id: #user.id, comment: #comment_params } }
Here is my comments controller create action
def create
#outlet = Outlet.find(params[:comment][:outlet_id])
#comment = #outlet.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to(#outlet)
end
end
I'm pretty sure it is not working, because the outlet_id that it is looking for is a nested parameter inside of the comments parameter. How would I fix my rspec test to have it look for a nested parameter?
Just pass your params as arguments to the post call, nesting as necessary, e.g.:
post :create, user_id: #user.id, comment: { outlet_id: #outlet.id }
I have a create action that calls an ActiveJob if the record is successfully saved.
def create
#object = Object.new(importer_params)
respond_to do |format|
if #object.save
MyJob.perform_later( #object.id )
format.html { redirect_to #object, notice: t('.notice') }
else
format.html { render :new }
end
end
end
I want to test that the Job is correctly called in a controller spec.
describe "POST #create" do
it {
expect {
post :create, { object: valid_attributes }
}.to change(Object, :count).by(1)
}
it {
expect {
post :create, { object: valid_attributes }
}.to have_enqueued_job(MyJob)
}
end
But I get
Failure/Error:
expect {
post :create, { object: valid_attributes }
}.to have_enqueued_job(MyJob)
expected to enqueue exactly 1 jobs, but enqueued 0
The first test is passing, so I know the Object is saved successfully. What is the correct way to test that an ActiveJob is enqueued?
If you need to check that your job has been enqueued several times, you can now do this:
expect {
3.times { HelloJob.perform_later }
}.to have_enqueued_job(HelloJob).at_least(2).times
I've always looked at the size of ActiveJob::Base.queue_adapter.enqueued_jobs to test if a job was called. giving the code
it 'does something' do
expect {
post :create, { object: valid_attributes }
}.to change {
ActiveJob::Base.queue_adapter.enqueued_jobs.count
}.by 1
end
You should make sure that you are setting the enqueued_jobs to an empty array after each spec to avoid any unexpected behaviour. You can do this in the spec/rails_helper.rb
In official docs here is have_enqueued_job matcher
The have_enqueued_job (also aliased as enqueue_job) matcher is used to check if given ActiveJob job was enqueued.
https://relishapp.com/rspec/rspec-rails/docs/matchers/have-enqueued-job-matcher
I have the following test. There are three it blocks. The first one doesn't use shoulda unlike the other two.
If I don't use the before block with post :create, product: attrs then the first test fails as expected. But If I put the before block there then the first test fails, but the other two pass. I have a uniqueness validation on product name, but that shouldn't be the problem as I'm using sequence with factory.
What should I do? How should I generally setup the data for testing when there are rspec and shoulda matchers present at the same time?
describe "when user logged in" do
before(:each) do
login_user #logged in user is available by calling #user
end
context "POST create" do
context "with valid attributes" do
let!(:profile) { create(:profile, user: #user) }
let!(:industry) { create(:industry) }
let!(:attrs) { attributes_for(:product, user_id: #user.id, industry_ids: [ industry.id ]).merge(
product_features_attributes: [attributes_for(:product_feature)],
product_competitions_attributes: [attributes_for(:product_competition)],
product_usecases_attributes: [attributes_for(:product_usecase)]
) }
it "saves the new product in the db" do
expect{ post :create, product: attrs }.to change{ Product.count }.by(1)
end
#If I don't use this the 2 tests below fail. If I use it, then the test above fails.
# before do
# post :create, product: attrs
# end
it { is_expected.to redirect_to product_path(Product.last) }
it { is_expected.to set_flash.to('Product got created!') }
end
end
end
factories
factory :product, class: Product do
#name { Faker::Commerce.product_name }
sequence(:name) { |n| "ABC_#{n}" }
company { Faker::Company.name }
website { 'https://example.com' }
oneliner { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
user
end
You can't have it both ways. If you execute the method you are testing in the before, then you can't execute it again to see if it changes the Product count. If you don't execute it in your before, then you must execute it in your example and therefore can't use the is_expected one liner format.
There are a variety of alternatives. Here is one that incorporates the execution of the method into all the examples.
describe "when user logged in" do
before(:each) do
login_user #logged in user is available by calling #user
end
describe "POST create" do
subject(:create) { post :create, product: attrs }
context "with valid attributes" do
let!(:profile) { create(:profile, user: #user) }
let!(:industry) { create(:industry) }
let!(:attrs) { attributes_for(:product, user_id: #user.id, industry_ids: [ industry.id ]).merge(
product_features_attributes: [attributes_for(:product_feature)],
product_competitions_attributes: [attributes_for(:product_competition)],
product_usecases_attributes: [attributes_for(:product_usecase)]
) }
it "saves the new product in the db" do
expect{ create }.to change{ Product.count }.by(1)
end
it("redirects") { expect(create).to redirect_to product_path(Product.last) }
it("flashes") { expect(create).to set_flash.to('Product got created!') }
end
end
end
I'm new to RSpec, I wonder why I didn't pass this test
before(:each) { get :index }
it "assigns all favorites as #favorites" do
favorite = FactoryGirl.create(:favorite)
expect(assigns(:favorites)).to eq([favorite])
end
It says
1) FavoritesController GET index assigns all favorites as #favorites
Failure/Error: expect(assigns(:favorites)).to eq([favorite])
expected: [#<Favorite id: 1, patient_id: 6, doctor_id: 5>]
got: #<ActiveRecord::Relation []>
(compared using ==)
Diff:
## -1,2 +1,2
-[#<Favorite:0x000000058a5ca0 id: 1, patient_id: 6, doctor_id: 5>]
+[]
It seems assigns(:favorites) got empty. I have tried another approach as well
def valid_attributes
doctor = FactoryGirl.create(:doctor)
patient = FactoryGirl.create(:patient)
FactoryGirl.attributes_for(:favorite, doctor_id: doctor.id, patient_id: patient.id)
end
it "assigns all favorites as #favorites" do
favorite = Favorite.create! valid_attributes
expect(assigns(:favorites)).to eq([favorite])
end
And it got the same error. Any inputs would be helpful for me and I'd like to ask if there is any way to simplify it.
Update
app/controllers/favorite_controller.rb
class FavoritesController < ApplicationController
before_action :set_favorite, only: [:destroy]
before_action :authenticate_user!
def index
#favorites = Favorite.where(:patient_id => current_user.id).order(id: :asc)
end
end
spec/controllers/favorite_controller_spec.rb
require 'spec_helper'
describe FavoritesController, type: :controller do
login_patient
describe "GET index" do
let!(:favorite) { FactoryGirl.create(:favorite) }
before { get :index }
it { expect(response).to render_template(:index) }
it { expect(response).to be_success }
it { expect(response).to have_http_status(200) }
it "blocks unauthenticated access", :skip_before do
expect(response).to redirect_to(new_user_session_path)
end
it "assigns all favorites as #favorites" do
expect(assigns(:favorites).to_a).to eq([favorite])
end
end
end
spec/support/controller_helpers.rb
module ControllerHelpers
def login_patient
before :each do |example|
unless example.metadata[:skip_before]
#request.env["devise.mapping"] = Devise.mappings[:user]
#patient = FactoryGirl.create(:patient)
sign_in :user, #patient
end
end
end
end
You are creating the records after you send the request so by the time the request finishes, the record you just created isn't included in the list of favorites. Change your test code to the following
let!(:favorite) { FactoryGirl.create(:favorite) }
before { get :index }
it "assigns all favorites as #favorites" do
expect(assigns(:favorites)).to eq([favorite])
end
This will probably still fail because assigns(:favorites) is an ActiveRecord::Relation object so you have to call to_a
expect(assigns(:favorites).to_a).to eq([favorite])
UPDATE:
Since the favorite is being filtered by patient, you have to make sure that the created favorite in the test belongs to the patient. You can do that by changing the favorite to
let!(:favorite) { FactoryGirl.create(:favorite, patient: #patient)
Try to create the records before the request:
let!(:favorite) { FactoryGirl.create(:favorite) }
before(:each) { get :index }
it "assigns all favorites as #favorites" do
expect(assigns(:favorites).to_a).to eq([favorite])
end