RSpec "count" change.by? - ruby-on-rails

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

Related

rspec #variable returning nil

I have an issue with my #attributes variable. I would like it to be accessible to keep my code dry, but currently, I have to restate the variable and set it to "values" to get my rspec test to work. What is a better way to do this without duplicating the values.
ref: Unexpected nil variable in RSpec
Shows that it is not accessible in describe, but there needs be another solution. When would "specify" be appropriate? I have not used it.
describe "When one field is missing invalid " do
before(:each) do
#user = create(:user)
#attributes = {"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}
end
values = {"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}
values.keys.each do |f|
p = values.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(#user, p)
cr.valid?
end
end
end
Update based on comments:
I would also like to use the values array hash in other tests. If I put it in the loop as stated, I would still have to repeat it in other places. Any other recommendations?
Update: I tried using let(),
describe "When one field is missing" do
let(:user) {Factorybot.create(:user)}
let(:attributes) = {{"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}}
attributes do |f|
p = attributes.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(user, p)
cr.valid?
end
end
end
but get the following error.
attributes is not available on an example group (e.g. a describe or context block). It is only available from within individual examples (e.g. it blocks) or from constructs that run in the scope of an example (e.g. before, let, etc).
In either of your snippets, you don't need attributes inside of your specs. It is data to generate specs. As such, it must live one level above.
describe "When one field is missing" do
let(:user) { Factorybot.create(:user) }
attributes = { "has_car" => "true", "has_truck" => "true", "has_boat" => "true", "color" => "blue value", "size" => "large value" }
attributes do |f|
p = attributes.except(f)
it "returns invalid when #{f.to_s} is missing" do
cr = CarRegistration::Vehicle.new(user, p)
cr.valid?
end
end
end
As you seem to have recognized, based on the other SO post you linked to, you can't refer to your instance variables out in your describe block. Just set it as a local variable as you've done.
Using let
describe "When one field is missing" do
let(:user) {Factorybot.create(:user)}
let(:attributes) = {{"has_car"=>"true", "has_truck"=>"true", "has_boat"=>"true", "color"=>"blue value", "size"=>"large value"}}
## The variables are used INSIDE the it block.
it "returns invalid when a key is missing" do
attributes do |f|
p = attributes.except(f)
cr = CarRegistration::Vehicle.new(user, p)
expect(cr.valid?).to eq(true) # are you testing the expectation? Added this line.
end
end
end
Personally I don't like writing test (like the above) which could fail for multiple reasons. Sergio is correct. But if you want to use let you have to make use of it from WITHIN the it block - this example shows that.

RSpec doesn't show full difference between return and expected

When a run a test with JSON, the rspec doesn't show the full spec, so I can't see the diference between return and expected.
The message of diff is shortened with ...
expected: "{\"id\":1,\"number\":1,\"sequential\":1,\"emitted_at\":\"2014-01-01T13:35:21.000Z\",\"status\":\"aut...erenceds_attributes\":[{\"id\":null,\"nfe_key\":\"42150707697707000148550010000020101000020105\"}]}"
got: "{\"id\":1,\"number\":1,\"sequential\":1,\"emitted_at\":\"2014-01-01T13:35:21.000Z\",\"status\":\"aut...erenceds_attributes\":[{\"id\":null,\"nfe_key\":\"42150707697707000148550010000020101000020105\"}]}"
aut...erenceds_attributes look in middle of message
My script test:
RSpec.describe InvoiceSerializer do
let(:invoice) do
build :invoice, :testing_serializer
end
subject { described_class.new invoice }
it "returns a json" do
expected = {
id: 1,
number: 1,
sequential: 1,
emitted_at: "2014-01-01T13:35:21.000Z",
status: "authorized",
invoice_bills_attributes: [{
id: nil,
expire_at: "2014-01-02T00:00:00.000Z",
value: "1.23"
}],
...
}.to_json
expect(subject.to_json).to eq expected
end
end
Example of error in my console
What gem/plugin or expectation that you use to check your test?
I use the console and Rubymine IDE.
Now I use:
puts "1 --> #{subject.to_json}"
puts "2 --> #{expected}"
And I don't like to write this for to debbug my test.
Set RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length to a high value
Update: as Yurri suggested, it might be better to better to set it to nil
This might help: https://github.com/waterlink/rspec-json_expectations
As a bonus, it allows you to specify your tests in terms of a subset of attributes, which can be used to create more granular tests.
To build on previous answers, and utilize the RSpec.configure syntax you'll want to use something like this:
RSpec.configure do |rspec|
rspec.expect_with :rspec do |c|
# Or a very large value, if you do want to truncate at some point
c.max_formatted_output_length = nil
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 }

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