expect{ subject }.to change(...).by(1) doesn't work - ruby-on-rails

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 }

Related

rspec to have changed by 1, but was changed by 2

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.

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

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 }

rspec testing callback worked

i am trying to test some callback before_save logic. But i am stack in this dark place where i cant figure out solution.
I have this method which updates some attributes before save:
def order_item_positions
Place.item_positions_reorder(item_position, city_id).each do |place|
new_item_position = place.item_position + 1
place.update_attributes(item_position: new_item_position)
end
end
What that method does, is changes all records above to +1 position!
and than i want to test it using rspec something like this:
describe "places order" do
let(:city){FactoryGirl.create(:city)}
let(:place){FactoryGirl.create(:place, city_id: city.id)}
let(:place_sec){FactoryGirl.create(:place, city_id: city.id)}
context "when placed before other record" do
it "should be placed before" do
place_sec.item_position = 1
place.item_position = 1
expect{
...somehow saving and triggering callback! //dont know how to do that :/
}.to change(place_sec, :item_position).from(1).to(2)
end
end
end
Any help would be much appreciated! :)
You should build model and then save it, i think:
describe "places order" do
let!(:city) { FactoryGirl.create(:city) }
let!(:place) { FactoryGirl.create(:place, city_id: city.id) }
let!(:place_sec) { FactoryGirl.build(:place, city_id: city.id) }
context "when placed before other record" do
it "should be placed before" do
place_sec.item_position = 1
place.item_position = 1
expect(place_sec.save).to change(place_sec, :item_position).from(1).to(2)
end
end
end
You didn't mention in what model you have this before_save method order_item_positions. So what should you save to call it. Just build this model, and then save.
A simple call to .save should do it:
expect{
place_sec.save
}.to change(place_sec, :item_position).from(1).to(2)

Simplifying and correct RSpec controller tests

I have a few RSpec controller tests. Some work, some don't, and I'm trying to figure out how on Earth to fix them up and make them more efficient
Ideally, I would like to see if I can get each spec into the following form
subject { ... }
it { ... }
it { ... }
it { ... }
Note that for all of my controller specs I've written macros for the actual controller actions. The macros are all tested and all work, and the names make it fairly obvious what they do.
My "Create" test:
formats ||= ["html", "js"]
formats.each do |format|
context "valid attributes" do
subject { do_post_create( :customer, valid_attributes, format ) }
its(:response_code) { should eq(302)}
it { should redirect_to admin_customer_path(Customer.find_by_id(???))}
it { expect { subject }.to change(Customer, :count).by(1) }
end
context "invalid attributes" do
subject { do_post_create( :customer, invalid_attributes, format ) }
its(:response_code) { should eq(200)}
it { should render_template :new }
it { expect { subject }.to_not change(Customer, :count).by(1) }
end
end
In that spec, I've been trying to figure out some way to get the ID of the newly created object from the post statement. I've tried "Customer.last", but that doesn't seem to work. Any thoughts?
My "Update" spec:
formats ||= ["html", "js"]
formats.each do |format|
context "valid attributes" do
let(:object) { FactoryGirl.create(:customer) }
subject { do_put_update( class_to_symbol(model), object.id, attributes, format ) }
its(:response_code) { should eq(302)}
it "does alter #{model}" do
do_put_update( class_to_symbol(model), object.id, attributes, format )
assigns(:customer).should eq(object)
flash[:notice].should =~ /Success/
object.reload
attributes.each do |key, value|
object.send(key.to_s).should eq(value)
end
end
end
context "invalid attributes" do
let(:object) { FactoryGirl.create("customer") }
let(:invalid_attributes) { {:username => "!"} }
subject { do_put_update( class_to_symbol(model), object.id, invalid_attributes, format ) }
its(:response_code) { should eq(200)}
it "does not alter #{model}" do
do_put_update( class_to_symbol(model), object.id, invalid_attributes, format )
assigns(:customer).should eq(object)
flash[:notice].should =~ /Fail/
object.reload
attributes.each do |key, value|
object.send(key.to_s).should_not eq(value)
end
end
end
end
In the Update test, I would like to try to express the second block in a more concise way, ideally in a way that I can use the same "subject" statement for all of the tests. Is that possible?
I think you're over-thinking these specs. Instead of trying to force every spec into a predefined format (subject/it/...) write the specs so that they clearly document what should happen, then try to refactor the code afterwards.
Case in point: the use of the implicit subject for controller actions. subject and its are meant to be used with an object, not a method, and only really make sense when used that way. So for example, this makes sense:
subject { [1, 2, 3, 4] }
its(:size) { should == 4 }
Here, it's absolutely clear what is being tested: a 4-element array has a size of 4.
However, when you write:
subject { do_post_create( :customer, valid_attributes, format ) }
its(:response_code) { should eq(302)}
it's not really clear where you are getting that response code from without inspecting the do_post_create action. You say that the names of the macros "make it fairly obvious what they do", but they don't make it fairly obvious what they will return, and this is key for using the implicit subject because it's the return value that becomes the subject.
It would be much clearer just to write:
it "responds with a 302" do
do_post_create(:customer, valid_attributes, format)
response.should eq(302)
end
I also don't recommend mixing specs with and without implicit subjects, since it makes it yet more confusing what you are actually testing. In your invalid attributes context block, for example, you set a subject, but then in your second spec you actually test assignment of customer (assigns(:customer).should eq(object)), so basically the subject is irrelevant for this test. (However by setting the subject here and then not using it you are actually sending a PUT request twice (through do_put_update), which is bound to cause problems -- again, another reason not to be making requests in a subject block.)
I could go on, but I think you get the picture. Making specs short and sweet is great if you can do it without hurting readability, but in this case I think you've gone overboard.
Just my two cents, hope it helps.
p.s. In case the views above seem a bit extreme, read the documentation for implicit subjects, where you'll see that they actually recommend against using implicit subjects at all in public-facing tests:
While the examples below demonstrate how subject can be used as a user-facing concept, we recommend that you reserve it for support of custom matchers and/or extension libraries that hide its use from examples.

Resources