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.
Related
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.
I was trying to practice Rspec, but seems I was confused about some rails part.
Here is Zombie.rb
class Zombie < ActiveRecord::Base
validates :name, presence: true
has_many :tweets
def hungry?
true
end
end
It seems when I create a Zombie instance, it will check the name attribute.
So, I wrote these Rspec code.
it 'should not be hungry after eating' do
#zombie = Zombie.create
#zombie.should_not be_valid
#zombie.hungry?.should be_truthy
end
Why it will pass? If the #zombie is not valid, why #zombie.hungry? will still return true
hungry? will always return true, therefore your expectation always passes.
That your instance is invalid doesn't mean that the instance is not valid Ruby. It is still a fully functional instance. The valid? method returns false, because the values of this instance are not valid to you, since you defined that it is not valid to you without a name.
From Ruby's point of view it is still a valid Zombie instance.
Btw since you just started to learn RSpec. You use the old RSpec syntax, you might want to learn the new syntax instead which would look like this:
describe Zombie do
context 'without a name' do
subject(:zombie) { Zombie.new }
it 'is not valid' do
expect(zombie).to_not be_valid
end
it 'is hungry' do
expect(zombie).to be_hungry
end
end
end
because your hungry? method always returns true
be_truthy # passes if obj is truthy (not nil or false) even if your
object is nil it's returns true
be_truthy
z = Zombie.create - It will not be created because of validation
z.hungry? => true
so your test passed
tl;dr Valid names don't get written to the database because the test fails, and invalid names do get written to the database because the test passes.
Edit: For clarification about the project and my question in general: As described in the book, this User model is set up as the beginning stages to allow a website user to eventually log in to a website. The database columns would be "name" and "email", and each row would be one user (assuming the user name and email were valid). I've edited my original post below for further clarification, and all edits are in italics.
Additionally, please only respond if you can explain the code as is in my post--don't suggest adding additional code to make it work. The textbook I'm working from asserts that this code should work as is, yet it seems to evaluate to the opposite that it should. Lastly, if you know other links that explain this in more detail, that is helpful; however, I have already read apidock, RoR API, and most of the SO links that show up in a Google search, and none have been helpful in explicating this problem.
I'm working through Michael Hartl's Ruby on Rails Tutorial. I'm in chapter 6 working through validation tests and I'm stuck on the validation for the presence of a name. It seems to be doing exactly the opposite of what the tutorial says it should do (i.e., validating a non-valid name entry), even though I've followed the tutorial exactly. I've also searched the web and SO for more details on how assert_not works, but can't find any helpful details. Here's the process I've went through.
Add a test that we know will fail:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com")
end
# resolves to true and passes
test "should be valid" do
assert #user.valid?
end
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
end
So far so good. I understand this code perfectly well, and although the second test's wording is awkward, I interpret assert_not #user.valid? to work like this: #user.valid? evaluates to true, since no validations have been set up, so the test thinks a name with all blank spaces is valid. This is preceded by an assert_not which converts a true value to false, and makes the test fail.
We add a validation test:
class User < ActiveRecord::Base
validates :name, presence: true
end
This sets a validation rule for the User model that ensures the :name object has content and is not nil, an empty string, or blank space. Still good so far.
Here's where things get tricky. At this point, the expression #user.valid? in our test should (and does) evaluate to false, since the validation rule won't allow a :name with only spaces in it. I've added comments alongside this code to help my mind see what values are assigned and evaluated in this process:
test "name should be present" do
#user.name = " "
# => name: " " email: "example#example.com"
assert_not #user.valid?
# => assert_not false => true
end
#user.valid? evaluates to false, which makes the entire assert_not expression evaluate to true, and thus the test passes. In pseudocode, this line could translate thusly: assert_not (is the user valid? no) ==> assert_not(false) ==> true. Similarly, "We do not assert that the user is false", which evaluates to true.
This is where my problem is: The test passes on an invalid value for :name, thus allowing a name consisting of blank spaces to be written to the database, when that's exactly the opposite of what we are trying to achieve, namely, preventing blank names from being written to db.
Conversely, I worked out this logic with a valid name:
test "name should be present" do
# => name: "Example User" email: "example#example.com"
assert_not #user.valid?
# assert_not true => false
end
In this case, since #user.valid? evaluates to true (since "Example User" is a valid name), assert_not #user.valid? evaluates to false, the test fails, and our valid name does not get written to the database. In pseudocode, this line could translate thusly: assert_not (is the user valid? yes) ==> assert_not(true) ==> false. Similarly, "We do not assert that the user is true", which evaluates to false. Because the test evaluates to false (even with a true/valid user name), this valid name is not written to the database.
If someone could explain how this makes any sense, I'd greatly appreciate it, and detailed answers walking me through it would be even better.
The test passes on an invalid value for
:name, thus allowing a name consisting of blank spaces to be written
to the database...
assert_not returns true (same as test passing) if the expression that follows, #user.valid?, returns false. In effect the test in section 3 says: We're not asserting that the user is valid. Since that expectation/assertion is correct, it returns true and the test passes. Nothing has been saved to the database.
You can add a validation as you add a string " " consist two spaces
class User < ActiveRecord::Base
validates :name, presence: true, allow_blank: false
validate :name_of_valid
def name_of_valid
self.errors.add :base, 'My string can not be empty' if self.name == " "
end
end
and then
test "name should be present" do
#user.name = " "
# => name: " " email: "example#example.com"
assert_not #user.valid?
# => assert_not true
end
I had the same problem. Only with presence: true the test fails and when I add allow_blank: false it passes.
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
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)