MiniTest Model Validation Test Failing - ruby-on-rails

I have a model 'Policy'. Within that model, I have presence validations for policy_holder and premium_amount. I'm attempting to write a MiniTest test for this model. For some reason, my tests are failing.
Here is my model:
class Policy < ApplicationRecord
belongs_to :industry
belongs_to :carrier
belongs_to :agent
validates :policy_holder, presence: true
validates :premium_amount, presence: true
end
And here are my tests:
require 'test_helper'
class PolicyTest < ActiveSupport::TestCase
test 'should validate policy holder is present' do
policy = Policy.find_or_create_by(policy_holder: nil, premium_amount: '123.45',
industry_id: 1, carrier_id: 1,
agent_id: 1)
assert_not policy.valid?
end
test 'should validate premium amount is present' do
policy = Policy.find_or_create_by(policy_holder: 'Bob Stevens', premium_amount: nil,
industry_id: 1, carrier_id: 1,
agent_id: 1)
assert_not policy.valid?
end
test 'should be valid when both policy holder and premium amount are present' do
policy = Policy.find_or_create_by(policy_holder: 'Bob Stevens', premium_amount: '123.45',
industry_id: 1, carrier_id: 1,
agent_id: 1)
assert policy.valid?
end
end
Here is the failure message:
Failure:
PolicyTest#test_should_be_valid_when_both_policy_holder_and_premium_amount_are_present [test/models/policy_test.rb:22]:
Expected false to be truthy.
The last test is failing when I believe is should be passing. This has me thinking that my other tests are not correct either.

There is a much easier way to test validations with less "carpet bombing" involved:
require 'test_helper'
class PolicyTest < ActiveSupport::TestCase
setup do
#policy = Policy.new
end
test "should validate presence of policy holder" do
#policy.valid? # triggers the validations
assert_includes(
#policy.errors.details[:policy_holder],
{ error: :blank }
)
end
# ...
end
This tests just that validation and not every validation on the model combined. Using assert policy.valid? will not tell you anything about what failed in the error message.
errors.details was added in Rails 5. In older versions you need to use:
assert_includes( policy.errors[:premium_amount], "can't be blank" )
Which tests against the actual error message. Or you can use active_model-errors_details which backports the feature.

So what's happening here is the validations are failing on the model.
.valid? will return true if there are no errors on the object when the validations are run.
Since you are clearly seeing a "false", that means one or more of the validations on the model are failing.
In a Rails console, you should try creating an object manually and casting it to a variable, then testing it to see the errors thusly:
test = Policy.new(whatever params are needed to initialize here)
# This will give you the object
test.valid?
#This will likely return FALSE, and NOW you can run:
test.errors
#This will actually show you where the validation failed inside the object
Regardless, this is almost assuredly a problem in the model and its creation.
Keep in mind, .errors won't work until AFTER you run .valid? on the object.

Related

Why adding association mark my model as not valid?

I'm developing a simple weather API in Rails. This API will give the forecast for a given day. The forecast will have hourly data about the wind, temperature, relative humidity, etc.
I have implemented a model for the Forecast. The forecast have an association "has_many" with the other models, for example, the Wind. I have developed the following model for the Wind object:
class Wind < ApplicationRecord
belongs_to :forecast, foreign_key: true
validates_presence_of :period
validates :velocity, numericality: true, allow_blank: true
validates :direction, length: { maximum: 2 }, allow_blank: true
end
As I am trying to use TDD, I have implemented the following tests (among others):
class WindTest < ActiveSupport::TestCase
setup do
#valid_wind = create_valid_wind
#not_valid_wind = create_not_valid_wind
end
test 'valid_wind is valid' do
assert #valid_wind.valid?
end
test 'valid_wind can be persisted' do
assert #valid_wind.save
assert #valid_wind.persisted?
end
test 'not_valid_wind is not valid' do
assert_not #not_valid_wind.valid?
end
test 'not valid wind cannot be persisted' do
assert_not #not_valid_wind.save
assert_not #not_valid_wind.persisted?
end
test 'not_valid_wind has error messages for period' do
assert_not #not_valid_wind.save
assert_not #not_valid_wind.errors.messages[:period].empty?
end
test 'not_valid_wind has error messages for velocity' do
assert_not #not_valid_wind.save
assert_not #not_valid_wind.errors.messages[:velocity].empty?
end
test 'not_valid_wind has error messages for direction' do
assert_not #not_valid_wind.save
assert_not #not_valid_wind.errors.messages[:direction].empty?
end
private
def create_valid_wind
valid_wind = Wind.new
valid_wind.direction = 'NO'
valid_wind.velocity = 2
valid_wind.period = '00-06'
valid_wind.forecast_id = forecasts(:one).id
valid_wind
end
def create_not_valid_wind
not_valid_wind = Wind.new
not_valid_wind.velocity = 'testNumber'
not_valid_wind.direction = '123'
not_valid_wind
end
end
This bunch of tests was passing before I add the association with forecast:
belongs_to :forecast, foreign_key: true
Indeed, if I remove that line, any test fails. But with that line in the model, the following tests are failing (they are false and the test expects true):
test 'valid_wind is valid' do
assert #valid_wind.valid?
end
test 'valid_wind can be persisted' do
assert #valid_wind.save
assert #valid_wind.persisted?
end
I am trying to understand why this is happening. Anyone knows why those tests are failing? Also, is there any proper way to test associations?
Thank you in advance.
test 'valid_wind can be persisted' do
assert #valid_wind.save
assert #valid_wind.persisted?
end
This test is close to worthless since you are only testing that the test setup is correct, it tells you nothing about the application under test.
Instead in your model tests you should test on a per validation basis:
test 'does not allow non numerical values for velocity' do
wind = Wind.new(velocity: 'foo')
wind.valid?
assert_match "is not a number", wind.errors.full_messages_for(:velocity)
end
test 'allows numerical values for velocity' do
wind = Wind.new(velocity: 3)
wind.valid?
refute(wind.errors.include?(:velocity))
end
Testing passing values is usually just marginally useful but can be valuable if bugs occur.
In your models you don't really need to worry about setting up completely valid records - your functional and integration tests will cover that anyways.

Testing Rails model validations with RSpec, without testing AR itself

Testing Rails model validations with RSpec, without testing AR itself
Lets as setup we have model User:
class User < ActiveRecord::Base
validate :name, presence: true, uniqueness: { case_sensitive: false }, on: :create
validate :password, presence: true, format: { with: /\A[a-zA-z]*\z/ }
end
A see several ways to test this:
it { expect(user).to validate_presence_of(:name).on(:create) }
or
it do
user = User.create(name: '')
expect(user.errors[:name]).to be_present
end
My main question is which of the approaches is better and why? Can suggest me different approach?
Additional questions:
How much should I test? As an example, I can write so many tests for the regex, but it will be hell for maintenance.
How much you think will be full test coverage in this example?
The functionalities of:
Rails being able to validate the presence of an arbitrary value on your model
errors being added to an object for an attribute that is missing when a validation for it is configured
are covered in the tests for Rails itself (specifically, in the ActiveModel tests).
That leaves needing to write the tests for the config that covers the business logic of your app eg validating the presence of the specific name attribute on your specific User class etc. In my opinion, the matchers from the shoulda-matchers gem should have you covered:
RSpec.describe User, type: :model do
subject(:user) { build(:user) } # assuming you're using FactoryGirl
describe 'validations' do
specify 'for name' do
expect(user).to validate_presence_of(:name).on(:create)
# NOTE: saving here is needed to test uniqueness amongst users in
# the database
user.save
expect(user).to validate_uniqueness_of(:name)
end
specify 'for password' do
expect(user).to validate_presence_of(:password)
expect(user).to allow_value('abcd').for(:password)
expect(user).to_not allow_value('1234').for(:password)
end
end
end
I think that unless you have specific custom error messages for your errors that you want to test for (ie you've overridden the default Rails ones), then tests like expect(user.errors[:name]).to be_present can be removed (even if you have custom errors, I still think they're of dubious value since those messages will become locale-dependent if you internationalise your app, so I'd test for the display of some kind of error on the page in a feature spec instead).
I can write so many tests for the regex, but it will be hell for maintenance.
I don't think you can really get around this when testing validations for format, so I'd suggest just write some representative test cases and then add/remove those cases as you discover any issues you may have missed, for example:
# use a `let` or extract out into a test helper method
let(:valid_passwords) do
['abcd', 'ABCD', 'AbCd'] # etc etc
end
describe 'validations' do
specify 'for password' do
valid_passwords.each do |password|
expect(user).to allow_value(password).for(:password)
end
end
end
How much you think will be full test coverage in this example?
I've gotten 100% code coverage from reports like SimpleCov when writing unit specs as described above.
These 2 of them should be used, because:
it { expect(user).to validate_presence_of(:name).on(:create) }
=> You are expecting the validate_presence_of should be run on create, this should be the test case for model
it do
user = User.create(name: '')
expect(user.errors[:name]).to be_present
end
=> You are expecting a side effect when creating user with your input, so this should be the test case for controller
Why you shouldn't remove 1 of them:
Remove the 1st test case: what happens if you do database validation level instead, you expect an active record level validation
Remove the 2nd test case: what happens on controller actually creates a new User, how do you expect the error returning!

Rspec's instance_double creating intermittent spec failures

I'm getting intermittent test failures when using instance_double.
I have a file with 4 specs in it. Here is the source:
require 'rails_helper'
describe SubmitPost do
before(:each) do
#post = instance_double('Post')
allow(#post).to receive(:submitted_at=)
end
context 'on success' do
before(:each) do
allow(#post).to receive(:save).and_return(true)
#result = SubmitPost.call(post: #post)
end
it 'should set the submitted_at date' do
expect(#post).to have_received(:submitted_at=)
end
it 'should call save' do
expect(#post).to have_received(:save)
end
it 'should return success' do
expect(#result.success?).to eq(true)
expect(#result.failure?).to eq(false)
end
end
context 'on failure' do
before(:each) do
allow(#post).to receive(:save).and_return(false)
#result = SubmitPost.call(post: #post)
end
it 'should return failure' do
expect(#result.success?).to eq(false)
expect(#result.failure?).to eq(true)
end
end
end
This is a Rails 4.1.4 application. Internally, SubmitPost sets submitted_at and calls save on the passed-in Post. My Post model looks like this:
class Post < ActiveRecord::Base
validates :title, presence: true
validates :summary, presence: true
validates :url, presence: true
validates :submitted_at, presence: true
scope :chronological, -> { order('submitted_at desc') }
end
It's super vanilla.
When I run rake, rspec, or bin/rspec, I get all all four tests failing 20% - 30% of the time. The error message is always:
Failure/Error: allow(#post).to receive(:submitted_at=)
Post does not implement: submitted_at=
If I label one of the specs with focus: true, that one spec will fail 100% of the time.
If I replace instance_double with double, all specs will succeed 100% of the time.
It appears that instance_double is having some difficulty inferring the methods available on the Post class. It also appears to be somewhat random and timing-based.
Has anyone run into this issue? Any ideas what might be wrong? Any sense of how to go about troubleshooting this? Naturally, inserting a debugging breakpoint causes the specs to pass 100% of the time.
The problem you are seeing is that ActiveRecord creates column methods dynamically. instance_double uses 'Post' to look up methods to verify you are stubbing them correctly (unless the class doesn't exist yet or has not been loaded).
When a prior spec loads the model, ActiveRecord will create those dynamic methods so your spec passes as RSpec can then find the methods (with a respond_to? call). When run in isolation the model hasn't been previously used and so ActiveRecord will not have created the dynamic methods yet and your test fails as you're experiencing.
The workaround for this is to force ActiveRecord to load the dynamic methods when they are called in your spec:
class Post < ActiveRecord::Base
def submitted_at=(value)
super
end
end
See the RSpec documentation for further explanation and workarounds for the problem:
https://www.relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/dynamic-classes

How do I validate and test the tandem creation of associated objects in Rails 4?

In my app, when a User is initialized, I want them to build 5 items. I've seen tests that assert there are, for example, expect(#user.items.count).to eq(5). However, I've been trying to validate the length of items and test the validation itself, not the number of objects associated with a user. Is this even possible? If so, what's the best way of going about this?
Here is the relevant code I have so far.
class User < ActiveRecord::Base
ITEMS_ALLOWED = 5
has_many :items
validates :items, length: {is: ITEMS_ALLOWED}
after_initialize :init_items
def init_items
ITEMS_ALLOWED.times { items.build }
end
...
My relevant test, using RSpec, Faker and FactoryGirl
describe User do
before :each do
#user = build(:user, username: "bob")
#user.save
end
it "is invalid with more than 5 items" do
user3 = build(:user)
user3.save
expect(user3.items.create(name: "test")).not_to be_valid
end
end
Currently the test tries to validate the item that's created. I tried to move the validation to the Item class instead, but I'm receiving the error, undefined method items for nil on the line that tries to call user.items.count.
class Item < ActiveRecord::Base
belongs_to :user
validates :number_of_items, length: {is: 5}
def number_of_items
errors.add("User must have exactly 5 items.") unless user.items.count == 5
end
end
================
Update: Failure Message when there are no validations in the Item class.
Failures:
1) User initialization is invalid with more than 5 items
Failure/Error: expect(user3.items.create(name: "test")).not_to be_valid
expected #<Item id: 16, name: "test", user_id: 3, photo: nil, created_at: "2014-01-14 00:24:11", updated_at: "2014-01-14 00:24:11", photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, description: nil> not to be valid
When you create your User instance, the init_items is being called and the Item instances are being created. However, the user's id is not defined at that point, so the user_id value of the created items is nil. This in turn results in the table's user method returning nil in your number_of_items validation.
When you remove the Item validations, then you're RSpec example will fail because you're doing a validation on an Item (i.e. the result of user3.items.create) rather than validating the resulting User. Instead, you can do something like this:
user3.items.create(name: "test")
expect(user3).to_not be_valid
I'd avoid using after_initialize. It is called whenever an object is instantiated, even after merely calling User.find. If you must use it, add a test for new_record? so that the items are only added for new User's.
An alternative approach is to write a builder method to use instead of User.new.
class User < ActiveRecord::Baae
ITEMS_ALLOWED = 5
has_many :items
validates :items, length { is: ITEMS_ALLOWED }
def self.build_with_items
new.tap do |user|
user.init_items
end
end
def init_items
ITEMS_ALLOWED.times { items.build }
end
end
describe User do
context "when built without items" do
let(:user) { User.new }
it "is invalid" do
expect(user.items.size).to eq 0
expect(user.valid?).to be_false
end
end
context "when built with items" do
let(:user) { User.build_with_items }
it "is valid" do
expect(user.items.size).to eq 5
expect(user.valid?).to be_true
end
end
end
This allows you to separate the item initialization from the user initialization, in case you end up wanting to have a User without items. In my experience, this works out better than requiring all newed up objects to be built the same way. The tradeoff is that you now need to use User.build_with_items in the new action in the controller.

Rails Custom Validators: Testing options

I'm trying to write up a rails gem that involves (amongst other things) some custom model validators...and I'm wondering how to test validation options.
To give an example, I'd like to write an rspec test for which a blank field returns valid if the allow_nil option is true, and invalid otherwise. The code works fine, but I can't think of an elegant way to test it. The code itself:
Module ActiveModel
module Validations
module ThirstyVals
class ValidatePrime < EachValidator
# Validate prime numbers
def validate_each(record, attr_name, value)
return if options[:allow_nil] && value.strip.length == 0
# Other validation code here
# ...
end
end
end
end
end
I'm currently testing through a dummy project, which is fine, but the only way I can think of to test the :allow_nil option is to write up a new attribute with :allow_nil set, and verify its functionality...which seems both excessive and pretty inelegant. There must be a more graceful way - any ideas appreciated. (Other tests below for posterity)
# ...
before(:each) do
#entry = Entry.new
end
describe "it should accept valid prime numbers" do
['7', '13', '29'].each do |n|
#entry.ticket = n
#entry.valid?('ticket').should be_true
end
end
describe "it should reject non-prime numbers" do
['4', '16', '33'].each do |n|
#entry.ticket = n
#entry.valid?('ticket').should be_false
end
end
have you considered testing the validator in isolation like so:
in validate_prime_spec.rb
require path_to_validator_file
describe ActiveModel::Validations::ThirstyVals::ValidatePrime do
context :validate_each do
it 'should do some stuff' do
subject.validate_each(record, attr_name, value).should #some expectation
end
end
end
then may I suggest that you need not test the allow_nil functionality of Rails validations due to the fact that it is already tested in Rails? (see: activemodel/test/cases/validations/inclusion_validation_test.rb line 44)

Resources