Rspec failure - validation - ruby-on-rails

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.

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.

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

Test if a method is called inside a module method

I have the following in my module:
module SimilarityMachine
...
def answers_similarity(answer_1, answer_2)
if answer_1.compilation_error? && answer_2.compilation_error?
return compiler_output_similarity(answer_1, answer_2)
elsif answer_1.compilation_error? || answer_2.compilation_error?
return source_code_similarity(answer_1, answer_2)
else
content_sim = source_code_similarity(answer_1, answer_2)
test_cases_sim = test_cases_output_similarity(answer_1, answer_2)
answers_formula(content_sim, test_cases_sim)
end
end
...
end
I would like to test these "if conditions", to ensure that the right methods are called (all these methods are from SimilarityMachine module). To do that, I have:
describe SimilarityMachine do
describe '#answers_similarity' do
subject { answers_similarity(answer_1, answer_2) }
let(:answer_1) { create(:answer, :invalid_content) }
context "when both answers have compilation error" do
let(:answer_2) { create(:answer, :invalid_content) }
it "calls compiler_output_similarity method" do
expect(described_class).to receive(:compiler_output_similarity)
subject
end
end
end
With both answers created I go to the right if (the first, and I'm sure of that because I tested before). However, my result is:
1) SimilarityMachine#answers_similarity when both answers have compilation error calls compiler_output_similarity method
Failure/Error: expect(described_class).to receive(:compiler_output_similarity)
(SimilarityMachine).compiler_output_similarity(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
What am I doing wrong?
I would check out Testing modules in rspec other questions related to testing modules.
I'm not completely clear on this, but in general, modules don't receive method calls. They are collections of methods that have to be "mixed in" through the extend method and the like.
Here's an example how to test a module method in isolation, taken from https://semaphoreci.com/community/tutorials/testing-mixins-in-isolation-with-minitest-and-rspec:
describe FastCar
before(:each) do
#test_obj = Object.new
#test_obj.extend(Speedable)
end
it "reports the speed" do
expect(#test_obj.speed).to eq "This car runs super fast!"
end
end

Show model validation errors with rspec

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

FactoryGirl sequence not incrementing

I'm using FactoryGirl to create instances of a date dimension model for a Rails-related gem. My factory looks like this:
FactoryGirl.define do
sequence :next_day do |n|
Date.new(2000,12,31) + n.days
end
factory :date_dimension do
the_date = FactoryGirl.generate(:next_day)
date {the_date.to_s}
calendar_year {the_date.strftime("%Y")}
(...other attributes created similarly to calendar_year)
end
end
Out of frustration I actually built a little test to show what's not working:
describe "working date factories" do
before(:all) do
#date_dimension = FactoryGirl.create(:date_dimension)
#jan_two = FactoryGirl.create(:date_dimension)
end
describe "sequence incrementing" do
it "returns a date dimension object ok" do
#date_dimension.date.should == "2001-01-01"
end
it "returns the next date in the sequence" do
#jan_two.date.should == "2001-01-02"
end
end
end
When I run that test, I get:
working date factories
sequence incrementing
returns a date dimension object ok
returns the next date in the sequence (FAILED - 1)
Failures:
1) working date factories sequence incrementing returns the next date in the sequence
Failure/Error: #jan_two.date.should == "2001-01-02"
expected: "2001-01-02"
got: "2001-01-01" (using ==)
I've read a bunch of other questions related to sequences, but it doesn't seem that I'm making the mistakes identified therein. It's a different (likely dumber) mistake. What is it?
I finally found an approach that works, and is probably a little better anyway. I still don't understand why the code above doesn't work - if someone can explain that to me (maybe with a reference to a doc or part of the source code), I'll go ahead and accept that answer - this post is just for those who follow. Here's what worked:
FactoryGirl.define do
factory :date_dimension do
sequence(:date) { |n| (Date.new(2000,12,31) + n.days).to_s }
calendar_year { Date.parse(date).strftime("%Y") }
day_of_week { Date.parse(date).strftime("%A") }
end
end
The code above passes this test:
describe "working date factories" do
before(:all) do
#date_dimension = FactoryGirl.create(:date_dimension)
#jan_two = FactoryGirl.create(:date_dimension)
end
describe "sequences" do
it "returns the proper first date in the sequence" do
#date_dimension.date.should == "2001-01-01"
#date_dimension.calendar_year.should == "2001"
#date_dimension.day_of_week.should == "Monday"
end
it "returns the next date in the sequence" do
#jan_two.date.should == "2001-01-02"
#jan_two.calendar_year.should == "2001"
#jan_two.day_of_week.should == "Tuesday"
end
end
end

Resources