rSpec Testing Rails using Shoulda matchers - ruby-on-rails

I have been starting to learn testing in my Rails app and am using rSpec and Shoulda.
I have the following test which works:
it { should respond_to(:park_name) }
However, what I don't understand is, what is this being run on? Is this being run on the Model itself or an instance of the model and if it's an instance of the Model then is it automatically using my Factory Girl factory?
Any simple explanations on what is actually occurring here?
UPDATE:
Ok, So I have this:
describe 'validations' do
subject { FactoryGirl.build(:coaster) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:speed) }
it { should validate_presence_of(:height) }
end
But the tests are failing. Any ideas?
Coaster.rb:
class Coaster < ActiveRecord::Base
extend FriendlyId
friendly_id :slug, use: :slugged
belongs_to :park
belongs_to :manufacturer
attr_accessible :name,
:height,
:speed,
:length,
:inversions,
:material,
:lat,
:lng,
:park_id,
:notes,
:manufacturer_id,
:style,
:covering,
:ride_style,
:model,
:layout,
:dates_ridden,
:times_ridden,
:order,
:on_ride_photo
scope :by_name_asc, lambda {
order("name ASC")
}
scope :made_from, lambda { |material|
where("material = ?", material)
}
scope :wooden, lambda {
made_from "wood"
}
scope :steel, lambda {
made_from "steel"
}
delegate :name, :location_1, :location_2, :location_3, :location_4,
to: :park,
allow_nil: true,
prefix: true
delegate :name, :url,
to: :manufacturer,
prefix: true
validates :name,
:presence => true
validates :height,
allow_nil: true,
numericality: {greater_than: 0}
validates :speed,
allow_nil: true,
numericality: {greater_than: 0}
validates :length,
allow_nil: true,
numericality: {greater_than: 0}
Test Results:
1) Coaster validations should require speed to be set
Failure/Error: it { should validate_presence_of(:speed) }
Expected errors to include "can't be blank" when speed is set to nil, got no errors
# ./spec/models/coaster_spec.rb:75:in `block (3 levels) in '
2) Coaster validations should require height to be set
Failure/Error: it { should validate_presence_of(:height) }
Expected errors to include "can't be blank" when height is set to nil, got no errors
# ./spec/models/coaster_spec.rb:76:in `block (3 levels) in '
SIMILAR QUESTION:
I have this test:
describe 'methods' do
subject { FactoryGirl.build(:coaster) }
it "should return a formatted string of coaster name at park name" do
name_and_park.should eq('Nemesis at Alton Towers')
end
end
Coaster.rb:
def name_and_park
[name, park.name].join (' at ')
end
Error when running the test:
2) Coaster methods should return a formatted string of coaster name at
park name
Failure/Error: name_and_park.should eq('Nemesis at Alton Towers')
NameError:
undefined local variable or method name_and_park' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_6:0x007f84f4161798>
# ./spec/models/coaster_spec.rb:111:inblock (3 levels) in '
It says name_and_park cannot be called but surely that method should be being called on the instance of Coaster that is being made in the subject line? No?

It's being run on "subject", which is either explicitly defined through the subject method or implicitly defined by passing in a class as the argument to describe, in which case an instance of that class is instantiated and made the subject of the test. See https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/explicit-subject and https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/implicit-subject
As for the answer to the Update question, in your model validations for :speed and :length, you have allow_nil: true, which is why those two tests are failing. Part of the definition of validates_presence_of is that nil is not a settable value.
As for your latest question, I think you may be confused about the use of implicit subjects. If should is used by itself, it will indeed default to whatever the subject is, but if you include a subject yourself, as you have in this case with name_and_park, it won't treat that as a method of the default subject, it must have a definition within the current namespace. In your case, you would need to say subject.name_and_park.should ....
On a related aside, StackOverflow is best used when you ask a specific question or related set of questions and get an answer. For a variety of reasons, it's not intended for ongoing debugging sessions. One of those reasons is that it becomes tedious tracking substantial, sequential updates of the original question and answer.

Related

Shoulda cannot properly validate case-sensitive unique

Using shoulda and FactoryGirl to test model validation.
Factory
FactoryGirl.define do
factory :tag do
value { Faker::Lorem.word }
user
end
end
Tag Model
class Tag < ApplicationRecord
validates :value,
presence: true,
uniqueness: { case_sensitive: false }
belongs_to :user
has_and_belongs_to_many :cards
end
Tag Spec
RSpec.describe Tag, type: :model do
describe 'validations' do
it { should validate_presence_of(:value) }
it { should validate_uniqueness_of(:value) }
end
describe 'associations' do
it { should belong_to(:user) }
# it { should have_and_belong_to_many(:cards) }
end
end
I get the following error when I run the test,
Failures:
1) Tag validations should validate that :value is case-sensitively unique
Failure/Error: it { should validate_uniqueness_of(:value) }
Tag did not properly validate that :value is case-sensitively unique.
After taking the given Tag, setting its :value to ‹"an arbitrary
value"›, and saving it as the existing record, then making a new Tag
and setting its :value to a different value, ‹"AN ARBITRARY VALUE"›,
the matcher expected the new Tag to be valid, but it was invalid
instead, producing these validation errors:
* value: ["has already been taken"]
* user: ["must exist"]
# ./spec/models/tag_spec.rb:6:in `block (3 levels) in <top (required)>'
# -e:1:in `<main>'
The correct way to test case_insensitive is to using matcher below,
it { should validate_uniqueness_of(:value).case_insensitive }
You can also write with ignoring_case_sensitivity:
it { should validate_uniqueness_of(:value).ignoring_case_sensitivity }

Trying to change environment variable within RSpec model spec

I've got one User model which has different validations based on an environmental variable ENV['APP_FOR']. This can either be "app-1" or "app-2". app-1 validates for username while app-2 validates for email address. Here is my User model spec for app-1:
require 'rails_helper'
RSpec.describe User, type: :model do
include Shared::Categories
before do
ENV['APP_FOR']='app-1'
end
context "given a valid User" do
before { allow_any_instance_of(User).to receive(:older_than_18?).and_return(true) }
it {should validate_presence_of :username}
end
end
And this is the User model spec for app-2
require 'rails_helper'
RSpec.describe User, type: :model do
include Shared::Categories
before do
ENV['APP_FOR']='app-2'
end
context "given a valid User" do
before { allow_any_instance_of(User).to receive(:older_than_18?).and_return(true) }
it {should validate_presence_of :email}
end
end
My problem is that the environment variable isn't being set as I would expect it to be in the before block. Any ideas on how to do this?
EDIT 1
Here is my validation implementation. I used a concern which I extend the user model with:
module TopDogCore::Concerns::UserValidations
extend ActiveSupport::Concern
included do
if ENV['APP_FOR'] == 'app-1'
validates :username,
presence: true,
uniqueness: true
elsif ENV['APP_FOR'] == 'app-2'
validates :email,
presence: true,
uniqueness: true
end
end
end
RSpec loads the subject class before running code in the examples. When you do this:
before do
ENV['APP_FOR'] = # ...
end
it is too late. The class definition has already been executed. You can see this for yourself by simply printing the value of ENV['APP_FOR'] from within the class definition (in your case, the included concern). It is nil, since the environment variable was not set when the class source file was loaded.
Deferring evaluation by using a lambda (as suggested here) ought to work. You might try using your own test instead of the one provided by shoulda_matchers, eg.:
expect(subject.valid?).to be false
expect(subject.errors[:username].blank?).to be false
Try it
module TopDogCore::Concerns::UserValidations
extend ActiveSupport::Concern
included do
validates :username,
presence: true,
uniqueness: true, if: -> { ENV['APP_FOR'] == 'app-1' }
validates :email,
presence: true,
uniqueness: true, if: -> { ENV['APP_FOR'] == 'app-2' }
end
end
Stub your constant like this
before do
stub_const("APP_FOR", 'app-2')
end
Checkout the Stubbing Constants docs

Rails 4 - concerns for generic validation

I just ran across Rails concerns and I want to use them for the validations of my models. But I want the validations to be generic, so that the validation is used only if the Class in which I include my concern has the attribute. I thought it would be easy, but I have tried many ways like using column_names, constantize, send and many other but nothing works. What is the right way to do it? The code:
module CommonValidator
extend ActiveSupport::Concern
included do
validates :email, presence: { message: I18n.t(:"validations.commons.email_missing") },
format: { with: /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i,
message: I18n.t(:"validations.commons.email_wrong_format"),
allow_blank: true } if self.column_names.include? :email
end
end
class Restaurant < ActiveRecord::Base
include CommonValidator
.
.
.
end
Restaurant of course has an email attribute. Is it possible to check the existence of an attribute in the class in which in include my concern? I want include my CommonValidations into many models which will not have email attribute. I'm using rails 4.
You can use respond_to? on the current instance as follows:
validates :email, presence: { message: I18n.t(:"validations.commons.email_missing") },
format: { with: /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i,
message: I18n.t(:"validations.commons.email_wrong_format"),
allow_blank: true },
if: lambda { |o| o.respond_to?(:email) }
Another option as suggested by #coreyward is to define a class extending EachValidator. For example, for email validation:
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}\z/i
record.errors[attribute] << (options[:message] || I18n.t(:"validations.commons.email_wrong_format"))
end
end
end
Then you could update the validation call as:
validates :email,
presence: { message: I18n.t(:"validations.commons.email_missing") },
email: true,
allow_blank: true
I was looking for something similar, but with custom validations.
I ended up with something that I think could be shared, including generic tests.
First, set up the concern app/models/concern/my_concern.rb.
Please note that we don't define the validate_my_field into a ClassMethods module.
module MyConcern
extend ActiveSupport::Concern
included do
validate :my_field, :validate_my_field
end
private
def validate_my_field
...
end
end
Include concern into your model app/models/my_model.rb
class MyModel < ActiveRecord::Base
include MyConcern
end
Load concerns shared examples in spec/support/rails_helper:
…
Dir[Rails.root.join('spec/concerns/**/*.rb')].each { |f| require f }
…
Create concern shared examples spec/concerns/models/my_field_concern_spec.rb:
RSpec.shared_examples_for 'my_field_concern' do
let(:model) { described_class } # the class that includes the concern
it 'has a valid my_field' do
instance = create(model.to_s.underscore.to_sym, my_field: …)
expect(instance).not_to be_valid
…
end
end
Then finally call shared examples into your model spec spec/models/my_model_spec.rb:
require 'rails_helper'
RSpec.describe MyModel do
include_examples 'my_field_concern'
it_behaves_like 'my_field_concern'
end
I hope this could help.

How does one put validations on individual ActiveModel/ActiveRecord objects?

You have a model, say, Car. Some validations apply to every Car instance, all the time:
class Car
include ActiveModel::Model
validates :engine, :presence => true
validates :vin, :presence => true
end
But some validations are only relevant in specific contexts, so only certain instances should have them. You'd like to do this somewhere:
c = Car.new
c.extend HasWinterTires
c.valid?
Those validations go elsewhere, into a different module:
module HasWinterTires
# Can't go fast with winter tires.
validates :speed, :inclusion => { :in => 0..30 }
end
If you do this, validates will fail since it's not defined in Module. If you add include ActiveModel::Validations, that won't work either since it needs to be included on a class.
What's the right way to put validations on model instances without stuffing more things into the original class?
There are several solutions to this problem. The best one probably depends on your particular needs. The examples below will use this simple model:
class Record
include ActiveModel::Validations
validates :value, presence: true
attr_accessor :value
end
Rails 4 only
Use singleton_class
ActiveSupport::Callbacks were completely overhauled in Rails 4 and putting validations on the singleton_class will now work. This was not possible in Rails 3 due to the implementation directly referring to self.class.
record = Record.new value: 1
record.singleton_class.validates :value, numericality: {greater_than: 1_000_000}
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
Rails 3 and 4
In Rails 3, validations are also implemented using ActiveSupport::Callbacks. Callbacks exist on the class, and while the callbacks themselves are accessed on a class attribute which can be overridden at the instance-level, taking advantage of that requires writing some very implementation-dependent glue code. Additionally, the "validates" and "validate" methods are class methods, so you basically you need a class.
Use subclasses
This is probably the best solution in Rails 3 unless you need composability. You will inherit the base validations from the superclass.
class BigRecord < Record
validates :value, numericality: {greater_than: 1_000_000}
end
record = BigRecord.new value: 1
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
For ActiveRecord objects, there are several ways to "cast" a superclass object to a subclass. subclass_record = record.becomes(subclass) is one way.
Note that this will also preserve the class methods validators and validators_on(attribute). The SimpleForm gem, for example, uses these to test for the existence of a PresenceValidator to add "required" CSS classes to the appropriate form fields.
Use validation contexts
Validation contexts are one of the "official" Rails ways to have different validations for objects of the same class. Unfortunately, validation can only occur in a single context.
class Record
include ActiveModel::Validations
validates :value, presence: true
attr_accessor :value
# This can also be put into a module (or Concern) and included
with_options :on => :big_record do |model|
model.validates :value, numericality: {greater_than: 1_000_000}
end
end
record = Record.new value: 1
record.valid?(:big_record) || record.errors.full_messages
# => ["Value must be greater than 1000000"]
# alternatively, e.g., if passing to other code that won't supply a context:
record.define_singleton_method(:valid?) { super(:big_record) }
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
Use #validates_with instance method
#validates_with is one of the only instance methods available for validation. It accepts one or more validator classes and any options, which will be passed to all classes. It will immediately instantiate the class(es) and pass the record to them, so it needs to be run from within a call to #valid?.
module Record::BigValidations
def valid?(context=nil)
super.tap do
# (must validate after super, which calls errors.clear)
validates_with ActiveModel::Validations::NumericalityValidator,
:greater_than => 1_000_000,
:attributes => [:value]
end && errors.empty?
end
end
record = Record.new value: 1
record.extend Record::BigValidations
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
For Rails 3, this is probably your best bet if you need composition and have so many combinations that subclasses are impractical. You can extend with multiple modules.
Use SimpleDelegator
big_record_delegator = Class.new(SimpleDelegator) do
include ActiveModel::Validations
validates :value, numericality: {greater_than: 1_000_000}
def valid?(context=nil)
return true if __getobj__.valid?(context) && super
# merge errors
__getobj__.errors.each do |key, error|
errors.add(key, error) unless errors.added?(key, error)
end
false
end
# required for anonymous classes
def self.model_name
Record.model_name
end
end
record = Record.new value: 1
big_record = big_record_delegator.new(record)
big_record.valid? || big_record.errors.full_messages
# => ["Value must be greater than 1000000"]
I used an anonymous class here to give an example of using a "disposable" class. If you had dynamic enough validations such that well-defined subclasses were impractical, but you still wanted to use the "validate/validates" class macros, you could create an anonymous class using Class.new.
One thing you probably don't want to do is create anonymous subclasses of the original class (in these examples, the Record class), as they will be added to the superclass's DescendantTracker, and for long-lived code, could present a problem for garbage collection.
You could perform the validation on the Car, if the Car extends the HasWinterTires module... For example:
class Car < ActiveRecord::Base
...
validates :speed, :inclusion => { :in => 0..30 }, :if => lambda { self.singleton_class.ancestors.includes?(HasWinterTires) }
end
I think you can just do self.is_a?(HasWinterTires) instead of the singleton_class.ancestors.include?(HasWinterTires), but I haven't tested it.
Have you thought about using a Concern? So something like this should work.
module HasWinterTires
extend ActiveSupport::Concern
module ClassMethods
validates :speed, :inclusion => { :in => 0..30 }
end
end
The concern won't care that it itself has no idea what validates does.
Then I believe that you can just do instance.extend(HasWinterTires), and it should all work.
I'm writing this out from memory, so let me know if you have any issues.
You likely want something like this, which is very similar to your original attempt:
module HasWinterTires
def self.included(base)
base.class_eval do
validates :speed, :inclusion => { :in => 0..30 }
end
end
end
Then the behavior should work as expected in your example, though you need to use include instead of extend on HasWinterTires.

RSpec crashing with multiple Rails validation helpers

I have two different validations for the :website attribute on my Customer model. One is the build in length helper, with the maximum set to 255, while the other is a custom validation. They both work individually, and the appropriate tests pass, but for some reason, when I run my tests with both validations, RSpec crashes to the point I have to complete exit out of Guard and restart it.
Here is my code, any way they are some how conflicting with each other? I have never experienced this before:
class Customer < Active Record::Base
...
URL_REGEX = /(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*#)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|#)|\/|\?)*)?$/i
validates :website, length: { maximum: 255 }
validate :valid_urls
private
def valid_urls
["website", "blog", "contact"].each do |attribute|
errors.add(attribute, "needs to be a valid url") if send(attribute).present? && URL_REGEX.match(send(attribute)).nil?
end
end
end
UPDATE: Thanks for the help, turned out the whole issue was just a bad regex. I had copied the regex from a stackoverflow thread, which had escaped some of the ampersands, producing a bad regex. I just now copied it from the jQuery validate source and it worked, sorry for the trouble.
Mackshkatz, can you try removing custom validation to use those provided by rails? As such:
class Customer < ActiveRecord::Base
validates :website, format: { with: URL_REGEX }, allow_blank: true, length: { maximum: 255 }
validates :blog, format: { with: URL_REGEX }, allow_blank: true
validates :contact, format: { with: URL_REGEX }, allow_blank: true
end
And see if it passes? It seems like the problem may be in complex regexp you are using.

Resources