Rspec for Validation - ruby-on-rails

In Model
validates :max_panels, :if => :depot?, :numericality => true
I am writing an rspec for the above validation and found something confusing
it { should validate_numericality_of(:max_panels) if :depot? }
When ran this test case got error like
1) Site spec for valid sites - Validations
Failure/Error: it { should validate_numericality_of(:max_panels) if :depot? }
Expected errors to include "is not a number" when max_panels is set to "abcd", got errors: ["format can't be blank (nil)", "illumination can't be blank (nil)", "illumination_period can't be blank (nil)", "vertical_size must be between 0.1 and 30 metres (nil)", "horizontal_size must be between 0.1 and 200 metres (nil)", "site_authorisation_id must have a valid authorisation (nil)"]
But when i added unless in my test case it got passed, Can anybody please explain me regarding it as i am new to Rspec. Also suggest how i can write the correct rspec for above validation.
it { should validate_numericality_of(:max_panels) if :depot? unless true }

Brief looking at the source of validate_numericality_of matcher shows that it doesn't contain explicit support for :if conditions. This may be handled by base matchers, but anyway, here's an alternative idea about the testing: prepare object, attempt validation and check error messages.
Something along the lines of:
describe 'numericality validation' do
subject(:instance) { described_class.new(params) }
before { instance.valid? }
context 'when depot' do
let(:params) { { max_panels: 'abcd', depot: true} }
it { expect(instance.errors.messages[:max_panels]).to eq 'is not a number' }
end
context 'when not depot' do
let(:params) { { max_panels: 'abcd', depot: false} }
it { expect(instance.errors.messages[:max_panels]).to eq nil }
end
end

unless true will never happen, so your it is likely not running the should and looks as though it passed.
unless is not rspec, that is pure ruby. You have told rspec you have a test (it), and that the should will never run. This will show as a passed test since the it did not fail.
Another part of this is to not use conditionals in your tests. They should be predictable. Don't use if or unless to determine whether or not to run assertions.
Lastly: Your error from rspec shows a list of validation errors, not of them say anything about what you're asserting. This test has multiple issues, but is flawed even in the should

Related

Testing if a method returns a `non-null` value

Im testing if my method populate() returns a non-null value (which it does, it returns an integer > 0) but having troubles to correctly write it. I have:
describe House::Room do
describe '.populate' do
let(:info) {
$info = {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to eq(nil)
end
end
end
You'll need to change the let assignment to:
describe House::Room do
describe '.populate' do
let(:info) {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to be(nil)
end
end
end
That should make your code work as you expect.
However, you could also use another matcher, like 'be_within' if you wanted to be more specific, or write several expect statements in the same test, like 'expect to be an integer', 'expect to be greater than 0', etc... There is no limit to the number of expect statements you can have in an 'it' block, the test will only pass if all of the expectations are fulfilled. (That said, I believe best practice would be to split it up into individual tests.)

Two conflicting shoulda matchers allow_value expressions are both passing

:speedis simply a numeric model attribute.
Both of these are passing:
it { is_expected.to allow_value( '1').for(:speed) }
it { is_expected.not_to allow_value( '1', 'fff' ).for(:speed) }
With a minor change, the second one is not passing:
it { is_expected.to allow_value( '1').for(:speed) }
it { is_expected.not_to allow_value( '1' ).for(:speed) }
Apparently, if there's a single passing value in the not_to allow_value expression the whole list of values passes.
I'm not quite understanding if it's working as intended and I'm doing it wrong, or if it's a bug.
It seems like it's a bug according to how the documentation explains it.

Rails 4 model custom validation with method - works in console but fails in spec

Attempting to write a custom model validation and having some trouble. I'm using a regular expression to confirm that a decimal amount is validated to be in the following format:
First digit between 0 and 4
Format as "#.##" - i.e. a decimal number with precision 3 and scale 2. I want 2 digits behind the decimal.
nil values are okay
while the values are nominally numeric, I decided to give the column a data type of string, in order to make it easier to use a regular expression for comparison, without having to bother with the #to_s method. Since I won't be performing any math with the contents this seemed logical.
The regular expression has been tested on Rubular - I'm very confident with it. I've also defined the method in the ruby console, and it appears to be working fine there. I've followed the general instructions on the Rails Guides for Active Record Validations but I'm still getting validation issues that have me headscratching.
Here is the model validation for the column :bar -
class Foo < ActiveRecord::Base
validate :bar_format
def bar_format
unless :bar =~ /^([0-4]{1}\.{1}\d{2})$/ || :bar == nil
errors.add(:bar, "incorrect format")
end
end
end
The spec for Foo:
require 'rails_helper'
describe Foo, type: :model do
let(:foo) { build(:foo) }
it "has a valid factory" do
expect(foo).to be_valid
end
describe "bar" do
it "can be nil" do
foo = create(:foo, bar: nil)
expect(foo).to be_valid
end
it "accepts a decimal value with precision 3 and scale 2" do
foo = create(:foo, bar: "3.50")
expect(foo).to be_valid
end
it "does not accept a decimal value with precision 4 and scale 3" do
expect(create(:foo, bar: "3.501")).not_to be_valid
end
end
end
All of these specs fail for validation on bar:
ActiveRecord::RecordInvalid:
Validation failed: bar incorrect format
In the ruby console I've copied the method bar_format as follows:
irb(main):074:0> def bar_format(bar)
irb(main):075:1> unless bar =~ /^([0-4]{1}\.{1}\d{2})$/ || bar == nil
irb(main):076:2> puts "incorrect format"
irb(main):077:2> end
irb(main):078:1> end
=> :bar_format
irb(main):079:0> bar_format("3.50")
=> nil
irb(main):080:0> bar_format("0.0")
incorrect format
=> nil
irb(main):081:0> bar_format("3.5")
incorrect format
=> nil
irb(main):082:0> bar_format("3.1234")
incorrect format
=> nil
irb(main):083:0> bar_format("3.00")
=> nil
The method returns nil for a correctly formatted entry, and it returns the error message for an incorrectly formatted entry.
Suspecting this has something to do with the validation logic, but as far as I can understand, validations look at the errors hash to determine if the item is valid or not. The logical structure of my validation matches the structure in the example on the Rails Guides, for custom methods.
* EDIT *
Thanks Lazarus for suggesting that I remove the colon from the :bar so it's not a symbol in the method. After doing that, most of the tests pass. However: I'm getting a weird failure on two tests that I can't understand. The code for the tests:
it "does not accept a decimal value with precision 4 and scale 3" do
expect(create(:foo, bar: "3.501")).not_to be_valid
end
it "generates an error message for an incorrect decimal value" do
foo = create(:foo, bar: "4.506")
expect(scholarship.errors.count).to eq 1
end
After turning the symbol :bar into a variable bar the other tests pass, but for these two I get:
Failure/Error: expect(create(:foo, bar: "3.501")).not_to be_valid
ActiveRecord::RecordInvalid:
Validation failed: 3 501 incorrect format
Failure/Error: foo = create(:bar, min_gpa: "4.506")
ActiveRecord::RecordInvalid:
Validation failed: 4 506 incorrect format
Any ideas why it's turning the input "3.501" to 3 501 and "4.506" to 4 506?
You use the symbol instead of the variable when checking against the regex or for nil.
unless :bar =~ /^([0-4]{1}\.{1}\d{2})$/ || :bar == nil
errors.add(:bar, "incorrect format")
end
Remove the : from the :bar
* EDIT *
It's not the specs that are failing but the model's validations upon creation. You should use build instead of create
Don't use symbol to refer an argument.
class Foo < ActiveRecord::Base
validate :bar_format
def bar_format
unless bar =~ /^([0-4]{1}\.{1}\d{2})$/ || bar == nil
errors.add(:bar, "incorrect format")
end
end
end
But if you want an regex for decimal like '1.0', '1.11', '1111.00' I advise you to use this regex:
/^\d+(\.\d{1,2})?$/
If you can to use regex for money, here is:
/^(\d{1,3}\,){0,}(\d{1,3}\.\d{1,2})$/
Good luck ^^

Rspec expect(method) vs expect(method).to be_true

I've just started diving into TDD with RSpec and Rails and I've found that if I'm testing a method that should come out to be true that the test will work if i use expect(method) instead of expect(method).to be_true.
it "is true" do
expect(subject).to be_true
#Works the same as...
expect(subject)
end
Is there any reason to not use the expect without .to, will it break the tests or not catch certain failures? Just wondering.
You are not achieving what you think you are. expect(false) does not fail. expect(true) only succeeds because nothing is being tested.
For example if you had a spec with the following:
describe "something" do
it "does something" do
false
puts expect(false)
# anything that evaluates to false
end
end
You would get something like this when you run rspec:
#<RSpec::Expectations::ExpectationTarget:0x007ff0b35a28b8\>
.
Finished in 0.00061 seconds
1 example, 0 failures
Essentially you are not asserting anything and expect(subject) evaluates to ExpectationTarget.
I think it is a coincidence that the latter works.
Every expect statement evaluates to true or false one way or the other.
For example
arr = ['a', 'b', 'c']
expect(arr).to include('a')
will return true because arr includes 'a'.
So in your case expect(subject) and expect(subject).to be_true is somewhat equivalent to the following:
bool = true
if bool
# Do something here
if bool == true
# Do something here
To make a long story short, it doesn't matter, but I would prefer expect(subject).to be_true for readability purposes.

Assert multiple change expectations within a single lambda request

I have a test like that:
lambda { post("/api/users", parameters) }.should change(User,:count).by(1)
lambda { post("/api/users", parameters) }.should_not change(ActionMailer::Base, :deliveries)
But I want to do it like that:
lambda { post("/api/users", parameters) }.should change(User,:count).by(1).and_not change(ActionMailer::Base, :deliveries)
Is it possible to do it without the need of two post calls?
Thanks
I have found a solution to test it.
lambda{
lambda { post("/api/users", params) }.should change(User,:count).by(1)
}.should change(ActionMailer::Base.deliveries, :count).by(1)
In my tests I am very strict: I want each test to test only a single thing. So I would always choose the first form, not the second.
Secondly I am not sure it is technically possible. The .should expects a block, which is executed before and after the lambda. Anyway, to my knowledge currently rspec does not support this (and imho with good reason).
I recently came across this issue when migrating some request tests over to feature test format for Capybara 2.1, and switching the testing syntax there from should-based to expect-based. To use the original question as the example, I had code like:
subject { -> { post("/api/users", parameters) } }
it { should change(User,:count).by(1) }
it { should_not change(ActionMailer::Base, :deliveries) }
Bringing this over to expect syntax in a scenario test presented some issues and yielded this kind of (working) clunkiness (sorry, not a big fan of explicitly nested lambdas/expects):
expect(-> { expect(post("/api/users", parameters)).to change(User,:count).by(1) }
).to_not change(ActionMailer::Base, :deliveries)
There are some great solutions to this issue in this StackOverflow thread which I tried and succeeded with, but what I ended up doing was simply follow the original format somewhat and split out each statement into its own scenario; something like:
feature "Add users via API" do
given(:posting_parameters_to_api) { -> { post("/api/users", parameters) } }
scenario "foo" do
expect(posting_parameters_to_api).to change(User,:count).by(1)
end
scenario "bar" do
expect(posting_parameters_to_api).to_not change(ActionMailer::Base,
:deliveries)
end
end
More verbose than the original request spec, but essentially working in the same way. Implementation will likely come down to personal taste.

Resources