When trying to write a unit test for models I keep getting the same error and cant seem to fix it.
This is my test:
require 'test_helper'
class ProductTest < ActiveSupport::TestCase
test "product attirbutes must not be empty" do
product = Product.new
assert product.invalid?
assert product.errors[:title].any?
assert product.errors[:description].any?
assert product.errors[:price].any?
assert product.errors[:image_url].any?
end
test "product price must be positive" do
product = Product.new(title: "My Book Title",
description: "yyy",
image_url: "zzz.jpg")
product.price = -1
# line number 19 below
assert product.invalid?
assert_equal ["must be greater than or equal to 0.01"],
product.errors[:price]
product.price = 0
assert product.invalid?
assert_equal ["must be greater than or equal to 0.01"],
product.errors[:price]
product.price = 1
assert product.valid?
end
end
When I run > rake test
I get the following error:
1) Failure:
ProductTest#test_product_price_must_be_positive
/test/models/product_test.rb:19]:
Failed assertion, no message given.
Here is my model:
class Product < ActiveRecord::Base
validates :title, :description, :image_url, presence: true
validates :price, numericality: {greater_then_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i,
message: 'must be a url for GIF, JPG or PNG image.'
}
end
I have no idea what is going on here please help!
As per the MiniTest::Assertions docs, any method derived from 'assert' will raise the exception message "Failed assertion, no message given" on a fail unless you supply the optional 'msg' paramater
A few things:
you are testing Active Record validations, these are tried and tested, production ready features of a library so testing it is not necessary. To become more familiar with Active Record validations just go the docs and then play around with your models in the rails console
Active record validations are only performed when attempt to 'create' or 'save'
a model. For example:
my_user = User.create(name: nil, email: nil) # try save to DB - fails silently
my_user.valid? # => false
my_user.errors.messages # => {name:["can't be blank"], email:["can't be blank"]}
Maybe explore some futher learning on the topic, the Ryan Bate's screencasts are great and free for the most part. Hope this helps
Note: I hoped to attach some more links/refrences however I do not have the Stackoverflow points to do
your model is wrong.
(line 3)
↓
wrong) validates :price, numericality: {greater_then_or_equal_to: 0.01}
correct) validates :price, numericality: {greater_than_or_equal_to: 0.01}
not "then" but "than".
Related
In my Rails6 app I've got two model validations which I want to test by Minitest:
class Portfolio < ApplicationRecord
validates :name, :status, presence: true
validates :initial_return do |record, attr, value|
record.errors.add(attr, 'Add value between -100 and 100') unless value >= -100 && value <= 100
end
end
Minitest:
class PortfolioTest < ActiveSupport::TestCase
setup do
#portfolio = Portfolio.create(name: Faker::Bank.name)
end
test 'invalid PortfolioSpotlightFigure, does not fit the range (-100, 100)' do
#portfolio.initial_return = -101
assert_not #portfolio.valid?
#portfolio.initial_return = 101
assert_not #portfolio.valid?
#portfolio.initial_return = 50
assert #portfolio.valid?
end
context 'validations' do
should validate_presence_of(:name)
end
end
Minitest gives the same error for both cases:
ArgumentError: You need to supply at least one validation
But when I remove validation for :initial_return field from Portfolio model:
validates :initial_return do |record, attr, value|
record.errors.add(attr, 'Add value between -100 and 100') unless value >= -100 && value <= 100
the test will pass for the validate_presence_of(:name) which means that I incorrectly defined that validation. What did I missed?
You don't need to reinvent the wheel
class Portfolio < ApplicationRecord
validates :name, :status, presence: true
validates :initial_return,
numericality: {
greater_than_or_equal_to: -100,
less_than_or_equal_to: 100
}
end
And stop carpet bombing your validations in your tests. Test the actual validation and not if the entire object is valid/invalid which leads to false positives and negatives. For example:
test 'invalid PortfolioSpotlightFigure, does not fit the range (-100, 100)' do
#portfolio.initial_return = -101
# these will pass even if you comment out the validation on initial_return as
# status is nil
assert_not #portfolio.valid?
#portfolio.initial_return = 101
assert_not #portfolio.valid?
# Will fail because status is nil
#portfolio.initial_return = 50
assert #portfolio.valid?
end
As you can see the test failures will tell you nothing about why the model is valid/invalid.
Instead use one assertion per test and test the actual validation:
class PortfolioTest < ActiveSupport::TestCase
setup do
# you dont need to insert records into the db to test associations
#portfolio = Portfolio.new
end
test 'initial return over 100 is invalid' do
# arrange
#portfolio.initial_return = 200
# act
#portfolio.valid?
# assert
assert_includes(#portfolio.errors.full_messages, "Initial return must be less than or equal to 100")
end
test 'initial return below -100 is invalid' do
# arrange
#portfolio.initial_return = -200
# act
#portfolio.valid?
# assert
assert_includes(#portfolio.errors.full_messages, "Initial return must be greater than or equal to -100")
end
test 'an initial return between -100 and 100 is valid' do
# arrange
#portfolio.initial_return = 50
# act
#portfolio.valid?
# assert
refute(#portfolio.errors.has_key?(:intial_return))
end
# ...
end
With shoulda you should be able to use the validates_numericality_of matcher:
should validate_numericality_of(:initial_return).
is_greater_than_or_equal_to(-100).
is_less_than_or_equal_to(100)
#portfolio = Portfolio.create(name: Faker::Bank.name) in the setup block is expected to already fail.
I don't know if it leads to the actual error but you can't create the object when you don't provide the initial initial_return. Since it runs against the validation itself.
Because the test case for the numerical range runs you need to make sure that you initial object is valid.
That's why it didn't fail when you removed the initial_return validation because the setup block was successful without the validation. You were just looking at the wrong end.
So you either use build which does not persist the object in the database and does not run the validation initially
#portfolio = Portfolio.build(name: Faker::Bank.name)
or if in case you want to persist the object in the database you have to make sure the setup object is valid
#portfolio = Portfolio.create(name: Faker::Bank.name, initial_return: 50)
I have a test:
test 'gift should not be saved with a non-numeric price' do
#gift = gifts(:gift_without_numeric_price)
assert_not #gift.save, 'gift was saved with non-numeric name'
end
It uses this fixture:
gift_without_numeric_price:
name: "pinecones"
description: "these pinecones smell really good"
estimated_price: "object"
link: "www.google.com"
My Gift model looks like this:
validates :estimated_price,
presence: true,
numericality: true
So I was expecting the test to pass since 'object' is not numeric, causing the #gift.save to return false and causing the assert_not to ultimately return true. However, the test fails for some reason. When I use the the direct way of creating a gift object, the test looks like this and the test passes:
test 'gift should not be saved with a non-numeric price' do
#gift = Gift.new(name: 'aa', description: 'description', link: 'www.google.com', estimated_price: 'object')
assert_not #gift.save, 'gift was saved with non-numeric name'
end
What am I doing wrong with the fixture?
You can try and change your validation:
validates :estimated_price, presence: true,numericality: { only_integer: true }
I'm trying to test validations on my Rails 4 application. The problem is that adding one specific validation all test passes, regardless if remaining validations are commented or not. Here's my code:
# test/models/company_test.rb
setup do
#company = companies(:one)
#company2 = companies(:two)
end
test "should not save company with invalid name" do
#company.name = ""
assert !#company.save, "Saved company without name"
#company2.name = #company.name
assert !#company2.save, "Saved company without unique name"
#company.name = "a"
assert !#company.save, "Saved company with shorter name than 2 characters"
#company.name = rand(36**65).to_s(36)
assert !#company.save, "Saved company with longer name than 64 characters"
end
test "should not save company if website is invalid" do
#company.website = "foo"
assert !#company.save, "Saved company with invalid website"
end
# app/models/company.rb
validates :name, :owner_id, presence: true, uniqueness: true
validates :name, length: { minimum: 2, maximum: 64 }
validates :website, :format => URI::regexp(%w(http https))
Now if I comment out name validations, all tests still passes. And then if I comment out website validation also, then all tests fail.
Didn't find what's wrong with this particular code, but got around it:
I've used regular expression from here and placed it with format validation defined in Ruby on Rails guide.
# app/models/company.rb
validates :website, format: { with: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+#)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+#)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%#.\w_]*)#?(?:[\w]*))?)/, message: "invalid url" }
This seems like kind of a no-brainer but I want to require a number and make sure it is not greater or less than a predetermined amount:
validates :age_min, presence: true, numericality: {
greater_than: 0, less_than_or_equal_to: :age_max
}
This test works as expected
test 'user should not be valid with age min greater than age max' do
user = FactoryGirl.build(:user, age_min: 30, age_max: 20)
assert !user.valid?
end
However, when I try and test that age_min is required:
test 'user should not be valid without age_min' do
user = FactoryGirl.build(:user, age_min: nil, age_max: 20)
assert !user.valid?
end
I get ArgumentError: comparison of Float with nil failed
It seems strange that Rails doesn't take the nil value into account, or am I missing something? It seems you should be able to get this to work without writing a custom validator, but perhaps I am mistaken.
Since your numericality validation for age_min id dependent on the value for age_max, and not a fixed value, I think you want to split your validation out and guard against nil values with procs
validates :age_min, :age_max, :presence => true
validates :age_min, :numericality => {greater_than: 0, less_than_or_equal_to: :age_max}, :unless => Proc.new {|user| user.age_min.nil? || user.age_max.nil? }
I'm new and trying to learn Rails using the book Agile Web Development with Rails, Fourth Edition (for Rails 3.2). Been able to get through all of the chapters so far without hiccups. If there were errors, it was usually my sloppy code (forgetting a comma, 'end' statement, etc.). But now I've hit a snag in the chapter on Unit Testing for Models. At the part where we're validating that the image URL ends with either .gif, .jpg, or .png.
I copied the code verbatim from the book for the depot/test/product_test.rb file:
test "image url" do
ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg http://a.b.c/x/y/z/fred.gif }
bad = %w{ fred.doc fred.gif/more fred.gif.more }
ok.each do |name|
assert new_product(name).valid?, "#{name} shouldn't be invalid"
end
bad.each do |name|
assert new_product(name).invalid?, "#{name} shouldn't be valid"
end
But when I run the rake test:units command, I get the following failure...
1) Failure:
test_image_url(ProductTest)[../depot/test/unit/product_test.rb:46]:
fred.gif shouldn't be invalid
4 tests, 13 assertions, 1 failures, 0 errors, 0 skips
rake aborted!
Does this mean that the image URL it's testing is invalid? Why is the test failing if what it says "fred.gif shouldn't be invalid" is correct?
I'm pretty confident that it's this part of the test that must be incorrect because the other tests I have in there (ex. "product attributes must not be empty", "product price must be positive", etc.) run just fine. I get no failures if I take out the "test image url" code block.
Please let me know what I'm doing wrong. If you need me to post the entirety of the ProductTest, I can.
UPDATE: There was a typo in my Products model that was causing the test to fail. All fixed now.
I had the same problem and I had to change my definition for new_product. In my initial definition, I had quotation marks around 'image url'. Once I removed the quotes, I was fine. Here's the code (my initial mistake was on the fifth line of my code):
def new_product(image_url)
Product.new(title: "My Book Title",
description: "yyy",
price: 1,
image_url: image_url)
end
test "image url" do
ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg
http://a.b.c/x/y/z/fred.gif }
bad = %w{ fred.doc fred.gif/more fred.gif.more }
ok.each do |name|
assert new_product(name).valid?, "#{name} shouldn't be invalid"
end
bad.each do |name|
assert new_product(name).invalid?, "#{name} shouldn't be valid"
end
end
As you can see, I didn't use the '_' in my test name and my tests passed.
I believe the problem is in the definition for function 'new_product'. Make sure the function is setting all fields to valid data. Mine wasn't setting the description to a valid value. I hope this helps. You probably already found this solution already but didn't update your post.
What I'm saying is that the product is failing due to some other field. The test that's failing is checking that there are no errors in a product constructed with each image_url. You just need to check that the constructor function, new_product, can construct a valid product.
Seems like you are missing underscore _
test "image url" do
should be
test "image_url" do
You can the paragraph from my file here:
test "image_url" do
ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg http://a.b.c/x/y/z.gif}
bad = %w{ fred.doc fred.gif/more fred.gif.more }
ok.each do |name|
assert new_product(name).valid?, "#{name} shouldn't be invalid"
end
bad.each do |name|
assert new_product(name).invalid?, "#{name} shouldn't be valid"
end
I was just struggling with this. There was also a typo in model. With my validations. I grouped the \Z with the png. It needs to be outside the capture group.
wrong:
class Product < ApplicationRecord
validates :title, :description, :image_url, presence: true
validates :price, numericality: { greater_than_or_equal_to: 0.01 }
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png\Z)}i, # <<<<<----
message: 'must be a URL for GIF, JPG, or PNG image.'
}
end
right:
class Product < ApplicationRecord
validates :title, :description, :image_url, presence: true
validates :price, numericality: { greater_than_or_equal_to: 0.01 }
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i, # <<<<<----
message: 'must be a URL for GIF, JPG, or PNG image.'
}
end