rspec to have changed by 1, but was changed by 2 - ruby-on-rails

I have following code to test action create of controller
let(:custom_action) { create :custom_action, entity: entity }
Describe '#create' do
context 'with valid attributes' do
before { allow(controller).to receive(:custom_actions_path).and_return('/') }
subject { post :create, params: { custom_action: {
name: custom_action.name,
label: custom_action.label,
url: custom_action.url,
request_method: custom_action.request_method,
entity_id: entity.id
},
locale: user.language }}
it 'should increment resource list by 1' do
expect { subject }.to change { CustomAction.count }.by(1)
end
end
end
When i running test i get: To have changed by 1, but was changed by 2
I checked action, if create new object always create one, not two.
Have I correct used subject? What wrong with my test? Thank you

Calling subject within your expect block evaluates the subject block. Subject block calls your action, but it needs to evaluate custom_action to do so. In the end, while evaluating the block, your custom_action block creates one CustomAction and your actual, controller action creates one more during the request.
Change your let to let! to fix your test.

Related

Rails rspec filtering test failed with 0 result and undefined method '[]'

I want to check if the filtering method for closed bank_offers is working properly. If I change status in one of the earlier defined bank_offer to :expired specs works well but I have to nest new bank_offer in to this context 'filtering'
not_working_specs
describe 'filtering' do
subject(:call) do
get endpoint, headers: authorized_headers(token: token), params: params
end
context "when status 'closed' passed " do
let(:credit_5) { create :credit, company: company, details: details }
let(:credit_process_5) { create :credit_process, credit: credit_5 }
let(:suggestion_5) { create :bank_suggestion, bank: user.bank }
let(:some_user) { create :bank_advisor, bank: user.bank }
before do
create :bank_offer,
credit_process: credit_process_5,
bank_suggestion: suggestion_5,
bank_advisor: some_user,
status: :expired,
created_at: 5.days.ago,
closed_at: 2.days.ago
params[:filters][:general_status] = 'closed'
end
it 'returns only closed bank offers', :aggregate_failures do
call
data = json_response_body['data']
expect(data.count).to eq(1)
expect(data.first['id']).to eq(second.id.to_s)
end
end
end
At the end I've got 2 errors:
1.1) Failure/Error: expect(data.count).to eq(1)
expected: 1
got: 0
and the second which is result from the first error:
1.2) Failure/Error: expect(data.first['id']).to eq(second.id.to_s)
NoMethodError:
undefined method `[]' for nil:NilClass
As I said before, the method works correctly because if I change the status of the previously defined bank_offer to closed, I will not have an error. Unfortunately, I need it in 20 other IT tests so I want to nest only one closed case.
Here are working specs, without nested bank attributes:
describe 'filtering' do
subject(:call) do
get endpoint, headers: authorized_headers(token: token), params: params
end
context "when status 'closed' passed " do
before do
params[:filters][:general_status] = 'closed'
end
it 'returns only closed bank offers', :aggregate_failures do
call
data = json_response_body['data']
expect(data.count).to eq(1)
expect(data.first['id']).to eq(second.id.to_s)
end
end
end

RSpec "count" change.by?

So I was looking at: https://rubyplus.com/articles/1491-Basic-TDD-in-Rails-Writing-Validation-Tests-for-the-Model
Just seeing techniques of testing and I saw this:
require 'rails_helper'
describe Article, type: :model do
it 'is valid if title and description fields have value' do
expect do
article = Article.new(title: 'test', description: 'test')
article.save
end.to change{Article.count}.by(1)
end
end
Specifically the last line: end.to change{Article.count}.by(1). From reading https://relishapp.com/rspec/rspec-expectations/v/3-7/docs/built-in-matchers/change-matcher
It says specifically:
The change matcher is used to specify that a block of code changes
some mutable state. You can specify what will change using either of
two forms:
Which makes sense. But were testing Article.count in the block of code which isn't actually "doing" anything (The article.save is what actually changed the Article.count so how exactly does this work? Does the test take a a look at whats in the block of code before it's ran and "prerun" it...the compare the .by(1) after?
Thanks
There are two blocks of code being executed. The block of code passed to expect, and the block of code passed to change. This is what's really happening, in pseudo-code.
difference = 1
initial_count = Article.count
article = Article.new(title: 'test', description: 'test')
article.save
final_count = Article.count
expect(final_count - initial_count).to eq(difference)
I would refactor your test to be a little easier to follow as this:
require 'rails_helper'
describe Article, type: :model do
let(:create_article) { Article.create(title: 'test', description: 'test') }
it 'is valid if title and description fields have value' do
expect { create_article }.to change { Article.count }.by(1)
end
end

Understanding ActiveRecord::Relations with RSpec example

I have the following ROR RSpec test:
Keep in mind that the test does pass as is in the code below. The method is correctly defined and does what is intended. The question is why when I modify and remove the [] around the #public_topic in the second example the test fails?
describe "scopes" do
before do
#public_topic = Topic.create!(name: RandomData.random_sentence, description: RandomData.random_paragraph)
#private_topic = Topic.create!(name: RandomData.random_sentence, description: RandomData.random_paragraph, public: false)
end
describe "visible_to(user)" do
it "returns all topics if user is present" do
user = User.new
expect(Topic.visible_to(user)).to eq(Topic.all)
end
it "returns only public topics if user is nil" do
expect(Topic.visible_to(nil)).to eq([#public_topic])
end
end
end
update
scope :visible_to, -> { where(public: true) }
It is hard to say without seeing the implementation of visible_to.
From the first example, it looks like that method returns an ActiveRecord::Relation object. That is going to represent a collection of objects and not a single object.
So, in essence, it comes down to:
object != [object]

expect{ subject }.to change(...).by(1) doesn't work

This test passes:
it 'test' do
old = parenthood.student.balance
Payment.money_transfer_to_child current_user: parenthood.user,
student_id: parenthood.student.id,
amount: '1',
comment: 'some comment'
expect(parenthood.student.reload.balance).to eq(old+1)
end
changing it to the following form makes it not pass ("was changed by 0"):
subject { Payment.money_transfer_to_child current_user: parenthood.user,
student_id: parenthood.student.id,
amount: '1',
comment: 'some comment' }
it { expect{ subject }.to change(parenthood.student.reload, :balance).by(1) }
What am I doing wrong?
I see that you figured out a way to make it work but maybe let's have a proper answer here. So first why it didn't work.
it { expect{ subject }.to change(parenthood.student.reload, :balance).by(1) }
When this line is executed what is done is (not exactly but roughly):
First the parameter of the change method are calculated. Which means that parenthood.student.reload is evaluated at that point.
The balance method is called on the object that was evaluated, and the value is remembered.
The block in expect (in this case only subject) is executed
Again the balance method is called on the object
The problem here is that parenthood.student.reload was evaluated before the subject was executed. After the execution of subject it was never calculated again as that was already passed to change method. That's why you were having the old value there.
What you can do is either have your solution (but it's not very elegant cause reloading parenthood is not part of what you are testing). Or you can use the block in change matcher and do something like:
it { expect{ subject }.to change { parenthood.student.reload.balance }.by(1) }
That syntax makes the block with parenthood.student.reload.balance evaluated both before running subject and after and should fix your test.
Adding one line to the subject solved my problem:
subject { Payment.money_transfer_to_child current_user: parenthood.user,
student_id: parenthood.student.id,
amount: '1',
comment: 'some comment'
parenthood.student.reload }

expected #count to have changed by 1, but was not given a block

I am testing my model method which returns me an Account object. I am checking whether my table has inserted a new row and my Model reflects its count.
Below is my spec.
it "can create an account" do
create_account = Account.create(account: acc)
create_account.should change(Account, :count).by(1);
end
Error i am getting
8) Account can create an account
Failure/Error: create_account.should change(Account, :count).by(1);
expected #count to have changed by 1, but was not given a block
The #change matcher expects a block in which some action is performed that effects the expected change. Try this:
expect { Account.create(account: acc) }.to change{ Account.count }.by(1)
See https://www.relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/expect-change
Happens when you use is_expected.to change { something } instead of expect { subject }.to change { something }

Resources