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
Related
I'm working on one old part of code.
before do
allow_any_instance_of(SportRateManager)
.to receive(:create)
.and_return(true)
end
There is Rubocop error like:
Avoid stubbing using 'allow_any_instance_of'
I read about RuboCop::RSpec:AnyInstance and I tried to change it like bellow.
From this
before do
allow_any_instance_of(SportRateManager)
.to receive(:create)
.and_return(true)
end
To this:
let(:sport_manager) { instance_double(SportRateManager) }
before do
allow(SportRateManager).to receive(:new).and_return(sport_manager)
allow(sport_manager).to receive(:create).and_return(true)
end
And with full context:
- before
describe 'POST create' do
let(:sport_rate) { build(:sport_rate) }
let(:action) { post :create, sport_rate: sport_rate.attributes }
context 'when sport rate manager created the rate successfully' do
before do
allow_any_instance_of(SportRateManager)
.to receive(:create)
.and_return(true)
end
it 'returns ok status' do
action
expect(response).to have_http_status(:ok)
end
end
... - after:
describe 'POST create' do
let(:sport_rate) { build(:sport_rate) }
let(:action) { post :create, sport_rate: sport_rate.attributes }
let(:sport_manager) { instance_double(SportRateManager) }
context 'when sport rate manager created the sport successfully' do
before do
allow(SportRateManager).to receive(:new).and_return(sport_manager)
allow(sport_manager).to receive(:create).and_return(true)
end
it 'returns ok status' do
action
expect(response).to have_http_status(:ok)
end
end
But this doesn't pass the test with error:
#<InstanceDouble(SportRateManager) (anonymous)> received unexpected message :sport_rate with (no args)
The solution was almost done. You probably need to add build :sport_rate before create
Sth like that
let(:sport_manager) { instance_double(SportRateManager) }
before do
allow(SportRateManager).to receive(:new).and_return(sport_manager)
allow(sport_manager).to receive(:sport_rate).and_return(build :sport_rate)
allow(sport_manager).to receive(:create).and_return(true)
end
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'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 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 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.