Show model validation errors with rspec - ruby-on-rails

I have one context and 7 expectations in a model spec:
describe User do
context "with all valid attributes" do
before { #user = FactoryGirl.build(:user_with_all_valid) }
subject { #user }
it { should be_valid }
its(:first_name) { should == "Jimmy" }
its(:last_name) { should == "Thehat" }
its(:profile_name) { should == "Jimbohatboy893" }
its(:email) { should == "awesomedog#hotmail.co.uk" }
its(:password) { should == "thisisasupersecretpassword12234234" }
its(:password_confirmation) { should == "thisisasupersecretpassword12234234" }
end
end
Running this I get some strange results:
14:35:13 - INFO - Running: spec
F......
Failures:
1) User with all valid attributes should be valid
Failure/Error: it { should be_valid }
expected valid? to return true, got false
# ./spec/model/user_spec.rb:29:in `block (3 levels) in <top (required)>'
Finished in 0.5358 seconds
7 examples, 1 failure
Okay so the validation expectation it { should be_valid } fails. Fair enough, but then why do all the other expectations, testing its first_name, last_name etc pass? If the validation doesn't pass, surely these attributes aren't written to the database and therefore these expectations shouldn't pass? Suspect I have the wrong idea here, they can't be written to the database. I would like to be able to test that though, for peace of mind.
My real question though is debugging. How can I print the validation error messages to the console? expected valid? to return true, got false is only describing the symptoms. I want first_name length too long or similar.

Sorry to tell but your tests are bad: whats their point? Test your factory's parameters?
What's the added value?
It would make more sense to check your model has fields in db (there are dedicated matchers but this is debatable) or respond_to the methods.
When you're working with an object instance, the fields are set in memory even if they are not persisted, it explains why your tests pass.
To get the errors you should add debug statements or simply check in console why your factory doesnt build valid objects.
it 'debugging...' do
puts subject.valid?
puts subject.errors
# or debugger
# or binding.pry
subject.should be_valid
end

Only a tiny little thing, but based on apneadiving's answer, this gives nice output regarding attributes and their errors:
it 'debugging...' do
subject.errors.each do |a, e|
puts a
puts e
puts "----"
end
end

Related

How to test an if-condition not been excuted?

I'm new to Rspec. I have a code like this:
if #context.persona == :system && #context.scopes&.include?(SEARCH_SCOPE)
return <something>
end
I want to write a unit test to confirm the #context.scopes&.include?(SEARCH_SCOPE) is not being executed when #context.persona is not :system. Here is what I wrote:
context 'when persona is system' do
let(:persona) { :system }
it 'checks the scope' do
allow(context).to receive(:scopes)
expect(context).to have_received(:scopes)
end
end
context 'when persona is not system' do
let(:persona) { :user }
it 'checks the scope' do
allow(context).to receive(:scopes)
expect(context).not_to have_received(:scopes)
end
end
The second test passed, but the first test failed with:
Failure/Error: expect(context).to have_received(:scopes)
(Double (anonymous)).scopes(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Could someone help me? I googled it before but didn't see anything helpful. I'm sorry if it is duplicated.
Not a direct answer to your question, but you are falling into the pit of testing implementation, instead of behaviour. Don't do that.
Your test shouldn't care about this:
expect(context).not_to have_received(:scopes)
Instead, your test should only be doing something like this:
context 'when persona is system and scopes includes SEARCH_SCOPE' do
let(:persona) { :system }
let(:scopes) { ... }
it 'returns <something>' do
expect(the_method_being_invoked).to eq(<something>)
end
end
context 'when persona is not system' do
let(:persona) { :user }
let(:scopes) { ... }
it 'returns <something-else>' do
expect(the_method_being_invoked).to eq(<something-else>)
end
end
context 'when scopes is empty' do
let(:persona) { :user }
let(:scopes) { nil }
it 'returns <something-else>' do
expect(the_method_being_invoked).to eq(<something-else>)
end
end
Why? Because when you refactor code, and the implementation changes, you don't want specs to start failing unless the behaviour has also changed.
You should usually even be able to write the test before writing the method -- therefore having no knowledge of its implementation details.

What a test should test with Rspec in Ruby on Rails application

I'm beginner in Rspec, and actually we asked me to do Rspec test for somes method which are already build but which never have been test (they don't write test before building the method).
And now I'm struggling to know how can I test my method, here is example:
class ConnectorJob < ActiveJob::Base
queue_as :connector
def perform(tenant_id = nil, debug = false)
tenant_to_sync = Tenant.to_sync(tenant_id)
return if tenant_to_sync.empty?
tenant_to_sync.each do |tenant|
service = MyAPP::ContactToSync.new(tenant, debug).call
if service.success?
ConnectorService::Synchronization.new(
tenant, service.data, debug
).call
end
end
end
end
What should I test on this? Should I test the return value is correct or if other method are well called?
Here is what I tried to do
require "rails_helper"
RSpec.describe ConnectorJob, type: :job do
it 'is in connector queue' do
expect(ConnectorJob.new.queue_name).to eq('connector')
end
describe 'perform' do
let (:tenant) { create(:tenant) }
let (:job) { ConnectorJob.new.perform(tenant.id) }
context 'with empty tenant' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to eq nil }
end
context 'with tenant' do
it { expect(ConnectorJob.new.perform(tenant.id)).to eq job }
end
end
end
As you can see my last test doesn't have sense but I have no idea what I should write on my Rspec for anticipate the result of this method.
If I check my Rspec coverage, Rspec is telling me I cover 100% of my method but I'm not sure that is correct.
I hope I'm clear, feel free to ask me more details.
Thank you all
I think you should test final result, I mean result after calling
ConnectorService::Synchronization.new(...).call and test three cases, e.g. if this call create new user, you should test it:
If tenant_to_sync.empty? == true
context 'with empty tenant' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to change(User.count).by(0) }
end
If service.success? == false
context 'MyAPP::ContactToSync return false' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to change(User.count).by(0) }
end
If service.success? == true
context 'success' do
it { expect(ConnectorJob.new.perform(#tenant.id)).to change(User.count).by(1) }
end
It should be enough to cover all scenarios.

Rspec failure - validation

When I run the following command rspec spec/models/vote_spec.rb, I get the following error:
Failures:
1) Vote validations value validation only allows -1 or 1 as values
Failure/Error: expect ( #post.votes ).to eq(-1)
NoMethodError:
undefined method `votes' for nil:NilClass
# ./spec/models/vote_spec.rb:7:in `block (4 levels) in <top (required)>'
Finished in 0.00255 seconds (files took 2.37 seconds to load)
1 example, 1 failure
Here's my code for vote_spec.rb
require 'rails_helper'
describe Vote do
describe "validations" do
describe "value validation" do
it "only allows -1 or 1 as values" do
expect ( #post.votes ).to eq(-1)
expect ( #post.votes ).to eq(1)
end
end
end
end
Sorry I am new to this, I guess my #post variable is not being set. Where should I look for this?
Correct. You're running into this error because your #post variable is nil. What do you mean by "where should I look for this?"
In order to fix this error, you need to define #post somehow in your spec, above the two "examples" in your it block. (This could go in the it block, or in a describe or let block above the it). Two options. Create the object long-form:
#post = Post.create(attribute_hash_here)
or use a factory of some sort (example below uses FactoryGirl):
#post = create(:post)
As it stands, however, were you to do that, your spec would still fail, because it has contrasting expectations:
expect ( #post.votes ).to eq(-1)
expect ( #post.votes ).to eq(1)
Unless the votes method on Post both returns a value AND alters that value, #post.votes will equal EITHER -1 or 1. So if it passes the first expectation, it will fail the second, and if it passes the second, it will fail the first.
** EDIT ** -- As ChrisBarthol pointed out, it's not clear why you need #post to exist at all. If you're just testing a vote's attribute validations, why not just test that object on its own?
First off these are model validations, and you are validating the vote model not the post model, so you should be setting #vote, and not #post. Secondly your test says you expect the value to equal -1 and then 1. How could it be both at the same time? Where are you setting the value such that you expect it? You have to restructure you tests so you are only testing one item at a time.
require 'rails_helper'
describe Vote do
let(:post) { Post.new(whatever post params) }
before { #vote=post.votes.build(whatever vote parameters you have) }
subject { #vote }
describe "validations" do
describe "+1 value valdiation" do
before { #vote.value = 1 }
it { should be_valid }
end
describe "-1 value valdiation" do
before { #vote.value = -1 }
it { should be_valid }
end
describe "other value valdiation" do
before { #vote.value = 0 }
it { should_not be_valid }
end
end
end
I'm guessing at your relationships. There are also better ways to write these tests but that should lead you down the right road.

The data must be identical, and the test should be successful. Data are identical, but the test failed

I am testing method last_photo:
def last_photo
#last_photo ||= user_updates.latest.where("photo_front IS NOT NULL and photo_front != ''").first.try(:photo_front)
end
Spec:
context "instance method" do
let(:user) { create :user }
context "last photo" do
before { create_list(:user_update, 3, user: user) }
let(:user_updates){ user.user_updates }
describe "#last_photo" do
subject { user.last_photo }
it { should eq user_updates.latest.first.photo_front }
end
end
end
the test should be successful. But there are strange error.
Attached GIST.
The answer is pretty simple really:
expected: #<PhotoUploader:0x00000007e34868 ...
got: #<PhotoUploader:0x00000007ebc100 ...
The values might be the same, but the objects are different in memory. Since you're doing a comparison on the objects, rspec expects the objects to be the exact same.
Now, user.user_updates and user_updates are two different variables in memory. You should do a comparison on the values.

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