Testing validations in model using RSpec with Rails - ruby-on-rails

I'm super new to testing my app using RSpec and I'm trying to test the validation of a comment without a user and keep getting syntax errors.
Here is the comment model code.
class Comment < ApplicationRecord
belongs_to :user
belongs_to :product
scope :rating_desc, -> { order(rating: :desc) }
validates :body, presence: true
validates :user, presence: true
validates :product, presence: true
validates :rating, numericality: { only_integer: true }
after_create_commit { CommentUpdateJob.perform_later(self, user) }
end
and here is the comment spec:
require 'rails_helper'
describe Comment do
before do
#product = Product.create!(name: "race bike", description: "fast race bike")
#user = User.create!(email: "jerryhoglen#me.com", password: "Maggie1!")
#product.comments.create!(rating: 1, user: #user, body: "Awful bike!")
end
it "is invalid without a user"
expect(build(:comment, user:nil)).to_not be_valid
end
end

What you're doing here is good - building objects and using the be_valid matcher. But if you use shoulda-matchers there's a one-liner to test a model validation:
describe Comment do
it { is_expected.to validate_presence_of :user }
end
You can do this for other validations such as uniqueness, numericality, etc, though you'd have to look up the syntax.

you missed a do, do it like:
it "is invalid without a user" do
expect(build(:comment, user: nil)).to_not be_valid
end
But that's not a very clear test when it fails, I suggest you check the actual expected validation error.
That's what it may look like:
expect(ValidatingWidget.new.errors_on(:name)).to include("can't be blank")
expect(ValidatingWidget.new(:name => "liquid nitrogen")).to have(0).errors_on(:name)
See rspec-rails errors_on # relishapp

Related

Rails minitest, why is my article model invalid?

I am starting to get into testing with minitest in Rails. Currently my first test to see if my model is valid is returning false. The error message itself is also very generic Expected false to be truthy. I have tested everything else and all of those tests work fine. Does anybody know what could be causing this?
article_test.rb
require 'test_helper'
class ArticleTest < ActiveSupport::TestCase
def setup
#article = Article.new(title:"Avengers Endgame", body:"I am inevitable")
end
test "article should be valid" do
assert #article.valid?
end
test "title should be present" do
#article.title = " "
assert_not #article.valid?
end
test "body should not be too short" do
#article.body = "aa"
assert_not #article.valid?
end
end
article.rb
class Article < ApplicationRecord
include Visible
belongs_to :user
has_many :comments, dependent: :destroy
has_rich_text :body
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
The main problem here is that you're using a poor method for testing your validations.
assert #article.valid? and assert_not #article.valid? does not actually tell you anything at all about the object under test. When the test fails you're none the wiser about why it actually failed and if the failure is actually even connected to what you're proportedly testing. At best it serves as a sort of litmus test that your test setup is correct.
Instead of this "carpet bombing" approach test each validation on its own:
class ArticleTest < ActiveSupport::TestCas
test "title should be present" do
article = Article.new(title: '')
article.valid?
assert_includes build_article.errors[:title], "can’t be blank"
end
test "body should not be too short" do
article = Article.new(body: 'aa')
article.valid?
assert_includes article.errors[:body], "is too short"
end
end
Testing all the validations at once (creating a record with valid input) will be covered by your integration and system tests anyways.
You have belongs_to :user, which expects the #article to have a user_id compulsorily to be present, before it can be saved.
If user_id is optional during creation, change this:
belongs_to :user
to
belongs_to :user, optional: true

Rails: Can't build object wth Factory Girl after adding accepts_nested_attributes_for

I cannot get Factory Girl to produce a User object when accepts_nested_attributes_for is enabled.
Given the models:
class Account < ApplicationRecord
belongs_to :meta, polymorphic: true, dependent: :destroy
end
class User < ApplicationRecord
has_one :account, as: :meta
validates :fname, presence: true
validates :lname, presence: true
validates_presence_of :account
#accepts_nested_attributes_for :account
end
And, the factories:
FactoryGirl.define do
factory :account do
sequence(:email) { |n| "account#{n}#mack.com" }
password "abcdef123"
password_confirmation "abcdef123"
confirmed_at Date.today
end
end
FactoryGirl.define do
factory :user do
sequence(:fname) { |n| "first#{n}" }
sequence(:lname) { |n| "last#{n}" }
after(:build) do |user|
user.build_account attributes_for(:account)
end
end
end
The following spec:
describe User, :type => :model do
it "has a valid factory" do
user = build(:user)
expect(user).to be_valid
end
end
Fails if I uncomment the accepts_nested_attributes_for in the User model. I would like to use the nested attributes for working with User objects, but I cannot figure out how to get FactorGirl to build a User with nested attributes enabled.
I get the following error:
1) User has a valid factory
Failure/Error: expect(user).to be_valid
expected #<User id: nil, fname: "first1", lname: "last1", created_at: nil, updated_at: nil> \
to be valid, but got errors: Account meta must exist
I am using Rails version 5.0.0beta1 and Factory Girl 4.6.0.
I've been battling this all day -- thanks for your help.
Edit: I see the same behavior using the rails console, so it would seem like the issue is independent of Factory Girl.

Rails uniqueness validation test failing

I'm starting out with Rails 4.2, and I'm try to test uniqueness for the Item models I'm making, I ran this code:
item.rb:
class Item < ActiveRecord::Base
attr_accessor :name
validates :name, uniqueness: true #, other validations...
end
item_test.rb:
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.new(name: "Example Item")
end
test "name should be unique" do
duplicate_item = #item.dup
#item.save
assert_not duplicate_item.valid?
end
end
but the test didn't pass, saying that the assert_not line is coming out true when it should be nil or false. I basically got this code from a tutorial but cannot figure out why it's not passing. Any help?
Edit: I found the solution, by not defining the other members (specifically :price ) of #item that I defined in the setup action, the test passed. However now I don't know how to make it pass with the the :price member. Below is the full implementation of item.rb & item_test.rb.
item.rb:
class Item < ActiveRecord::Base
attr_accessor :name, :description, :price
validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
validates :description, presence: true,
length: { maximum: 1000 }
VALID_PRICE_REGEX = /\A\d+(?:\.\d{0,2})?\z/
validates :price, presence: true,
:format => { with: VALID_PRICE_REGEX },
:numericality => {:greater_than => 0}
end
item_test.rb:
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.new(name: "Example Item", description: "Some kind of item.", price: 1.00)
end
test "name should be unique" do
duplicate_item = #item.dup
#item.save
assert_not duplicate_item.valid?
end
end
Almaron's answer above is correct and should be the accepted answer.
I am adding this answer to elaborate on it.
The test would be as follows:
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.create(name: "Example Item")
end
test "name should be unique" do
duplicate_item = #item.dup
assert_not duplicate_item.valid?
end
end
Note: duplicate_item need not be saved before validating it.
The uniqueness validation is performed against the records already existing in the database. And your Item.new(name: "Example Item") is not in the database untill it is saved. So if you use Item.create(name: "Example Item") instead, the test should pass.
You've identified at least some of the problem in your edit.
The problem isn't that you're using Item.new instead of Item.create the problem is that when you do #item.save the #item record isn't being saved because it has other validation issues.
You could try...
#item.save(validate: false)
... which will force #item to be written to the database, but the test doesn't really determine why the duplicate_item record is invalid.
Better might be to test that you have an error relating to name...
require 'test_helper'
class ItemTest < ActiveSupport::TestCase
def setup
#item = Item.new(name: "Example Item")
end
test "name should be unique" do
duplicate_item = #item.dup
#item.save(validate: false)
duplicate_item.valid? # need this to populate errors
assert duplicate_item.errors
assert duplicate_item.errors[:name]
end
end
I fixed it, I got rid of the attr_accessor line, the test was then able to access the attributes and was able to detect the duplication.

Rails model default_value method conflict with presence true

I have this simplified model:
class Contract < ActiveRecord::Base
belongs_to :user belongs_to :plan
before_validation :set_default_is_voided
before_validation :set_default_expiration
validates :user, presence: true
validates :plan, presence: true
validates :contract_date, presence: true
validates :is_voided, presence: true
validates :expiration, presence: true
protected
def set_default_is_voided
if self.is_voided.nil?
self.is_voided = false
ap self.is_voided.present?
ap self.is_voided
end
end
def set_default_expiration
if self.contract_date.present?
self.expiration = self.contract_date+1.month
end
end
end
And this rspec simplified test:
context "Should create default values" do
it "Have to create is_voided" do
user = FactoryGirl.create(:user)
plan = FactoryGirl.create(:planContract)
ap "HERE"
contractDefault = FactoryGirl.create(:contractDefault, plan: plan, user: user)
ap contractDefault
expect(contractDefault.is_voided).to eq(false)
end
it "Have to create expiration" do
#expect(contract.expiration).should eq(Date.today()+1.month)
end
end
FactoryGirl:
FactoryGirl.define do
factory :contractVoid, class:Contract do
end
factory :contractDefault, class:Contract do
contract_date Date.today
end
end
This test fail with an 'is_voided can't be blank'.
And the question is:
Why the method "set_default_is_voided" in before_validation don't pass the presence true validation? Moreover, the self.is_voided.present? return false, why is it happing?
You answered your own question as to why set_default_is_voided doesn't pass the the presence: true validation, namely that self.is_voided.present? returns false, which is how presence: true is determined.
self.is_voided.present? returns false because false.present? == false per A concise explanation of nil v. empty v. blank in Ruby on Rails
See Rails: how do I validate that something is a boolean? for one way to validate that a boolean field is not nil.
See http://www.quora.com/Why-does-Rails-make-false-blank-true for a Q&A on the motivation behind the definition of blank?.

How do I test validity of a model object only on update using rspec?

I have a profile model like so;
class Profile < ActiveRecord::Base
attr_accessible :first_name, :last_name
belongs_to :user
validates :user, presence: true
validates :first_name, presence: true, on: :update
validates :last_name, presence: true, on: :update
end
I am wanting to write some rspec tests to test the validation of first_name and last_name but I cannot see how to run profile.should_not be_valid only on update in a model test. Something like;
it "should be invalid without a first name on update" do
profile = FactoryGirl.build :profile
profile.first_name = nil
profile_should_not be_valid
end
doesn't differentiate between an update or create action and I can't see anything in the rspec documentation about this. Surely it's a fairly common thing to test.
be_valid in rspec just calls valid? on the model.
profile = FactoryGirl.build :profile
This builds a new model instance for Profile, but does not commit it to the database. You would use this profile for your create test. Setting :first_name to nil should and calling profile.should be_valid should pass.
profile = FactoryGirl.create :profile
This builds and inserts the model instance for Profile into the database. You would use this profile for your update test. Setting :first_name to nil should and calling profile.should be_valid should fail.

Resources