Add validates_absence_of matcher

This commit is contained in:
jmpage 2013-12-05 11:38:30 -05:00 committed by Elliot Winkler
parent aff7bd7e2a
commit 2cdae242ca
5 changed files with 253 additions and 1 deletions

View File

@ -21,6 +21,8 @@
* You can now test that your `has_many :through` or `has_one :through`
associations are defined with a `:source` option.
* Add `validates_absence_of` matcher.
# v 2.4.0
* Fix a bug with the `validate_numericality_of` matcher that would not allow the

View File

@ -38,7 +38,7 @@ Different matchers apply to different parts of Rails:
### ActiveModel Matchers
*Jump to: [allow_mass_assignment_of](#allow_mass_assignment_of), [allow_value / disallow_value](#allow_value--disallow_value), [ensure_inclusion_of](#ensure_inclusion_of), [ensure_exclusion_of](#ensure_exclusion_of), [ensure_length_of](#ensure_length_of), [have_secure_password](#have_secure_password), [validate_acceptance_of](#validate_acceptance_of), [validate_confirmation_of](#validate_confirmation_of), [validate_numericality_of](#validate_numericality_of), [validate_presence_of](#validate_presence_of), [validate_uniqueness_of](#validate_uniqueness_of)*
*Jump to: [allow_mass_assignment_of](#allow_mass_assignment_of), [allow_value / disallow_value](#allow_value--disallow_value), [ensure_inclusion_of](#ensure_inclusion_of), [ensure_exclusion_of](#ensure_exclusion_of), [ensure_length_of](#ensure_length_of), [have_secure_password](#have_secure_password), [validate_absence_of](#validate_absence_of), [validate_acceptance_of](#validate_acceptance_of), [validate_confirmation_of](#validate_confirmation_of), [validate_numericality_of](#validate_numericality_of), [validate_presence_of](#validate_presence_of), [validate_uniqueness_of](#validate_uniqueness_of)*
Note that all of the examples in this section are based on an ActiveRecord
model for simplicity, but these matchers will work just as well using an
@ -312,6 +312,39 @@ class UserTest < ActiveSupport::TestCase
end
```
#### validate_absence_of
The `validate_absence_of` matcher tests the usage of the
`validates_absence_of` validation.
```ruby
class Tank
include ActiveModel::Model
validates_absence_of :arms
validates_absence_of :legs,
message: "Tanks don't have legs."
end
# RSpec
describe Tank do
it { should validate_absence_of(:arms) }
it do
should validate_absence_of(:legs).
with_message("Tanks don't have legs.")
end
end
# Test::Unit
class TankTest < ActiveSupport::TestCase
should validate_absence_of(:arms)
should validate_absence_of(:legs).
with_message("Tanks don't have legs.")
end
```
#### validate_acceptance_of
The `validate_acceptance_of` matcher tests usage of the

View File

@ -7,6 +7,7 @@ require 'shoulda/matchers/active_model/disallow_value_matcher'
require 'shoulda/matchers/active_model/ensure_length_of_matcher'
require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
require 'shoulda/matchers/active_model/ensure_exclusion_of_matcher'
require 'shoulda/matchers/active_model/validate_absence_of_matcher'
require 'shoulda/matchers/active_model/validate_presence_of_matcher'
require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'

View File

@ -0,0 +1,77 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
# Ensures that the model is not valid if the given attribute is present.
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
# Defaults to the translation for <tt>:present</tt>.
#
# Examples:
# it { should validate_absence_of(:name) }
# it { should validate_absence_of(:name).
# with_message(/may not be set/) }
def validate_absence_of(attr)
ValidateAbsenceOfMatcher.new(attr)
end
class ValidateAbsenceOfMatcher < ValidationMatcher # :nodoc:
def with_message(message)
@expected_message = message
self
end
def matches?(subject)
super(subject)
@expected_message ||= :present
disallows_value_of(value, @expected_message)
end
def description
"require #{@attribute} to not be set"
end
private
def value
if reflection
obj = reflection.klass.new
if collection?
[ obj ]
else
obj
end
elsif attribute_class == Fixnum
1
elsif !attribute_class || attribute_class == String
'an arbitrary value'
else
attribute_class.new
end
end
def attribute_class
@subject.class.respond_to?(:columns_hash) &&
@subject.class.columns_hash[@attribute].respond_to?(:klass) &&
@subject.class.columns_hash[@attribute].klass
end
def collection?
if reflection
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
else
false
end
end
def reflection
@subject.class.respond_to?(:reflect_on_association) &&
@subject.class.reflect_on_association(@attribute)
end
end
end
end
end

View File

@ -0,0 +1,139 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher do
if active_model_4_0?
context 'a model with an absence validation' do
it 'accepts' do
validating_absence_of(:attr).should validate_absence_of(:attr)
end
it 'does not override the default message with a present' do
validating_absence_of(:attr).should validate_absence_of(:attr).with_message(nil)
end
end
context 'a model without an absence validation' do
it 'rejects' do
model = define_model(:example, attr: :string).new
model.should_not validate_absence_of(:attr)
end
end
context 'an ActiveModel class with an absence validation' do
it 'accepts' do
active_model_validating_absence_of(:attr).should validate_absence_of(:attr)
end
it 'does not override the default message with a blank' do
active_model_validating_absence_of(:attr).should validate_absence_of(:attr).with_message(nil)
end
end
context 'an ActiveModel class without an absence validation' do
it 'rejects' do
active_model_with(:attr).should_not validate_absence_of(:attr)
end
it 'provides the correct failure message' do
message = %{Expected errors to include "must be blank" when attr is set to "an arbitrary value", got no errors}
expect { active_model_with(:attr).should validate_absence_of(:attr) }.to fail_with_message(message)
end
end
context 'a has_many association with an absence validation' do
it 'requires the attribute to not be set' do
having_many(:children, absence: true).should validate_absence_of(:children)
end
end
context 'a has_many association without an absence validation' do
it 'does not require the attribute to not be set' do
having_many(:children, absence: false).
should_not validate_absence_of(:children)
end
end
context 'an absent has_and_belongs_to_many association' do
it 'accepts' do
model = having_and_belonging_to_many(:children, absence: true)
model.should validate_absence_of(:children)
end
end
context 'a non-absent has_and_belongs_to_many association' do
it 'rejects' do
model = having_and_belonging_to_many(:children, absence: false)
model.should_not validate_absence_of(:children)
end
end
context "an i18n translation containing %{attribute} and %{model}" do
after { I18n.backend.reload! }
it "does not raise an exception" do
stub_translation("activerecord.errors.messages.present",
"%{attribute} must be blank in a %{model}")
expect {
validating_absence_of(:attr).should validate_absence_of(:attr)
}.to_not raise_exception
end
end
context "an attribute with a context-dependent validation" do
context "without the validation context" do
it "does not match" do
validating_absence_of(:attr, on: :customisable).should_not validate_absence_of(:attr)
end
end
context "with the validation context" do
it "matches" do
validating_absence_of(:attr, on: :customisable).should validate_absence_of(:attr).on(:customisable)
end
end
end
def validating_absence_of(attr, options = {})
define_model :example, attr => :string do
validates_absence_of attr, options
end.new
end
def active_model_with(attr, &block)
define_active_model_class('Example', accessors: [attr], &block).new
end
def active_model_validating_absence_of(attr)
active_model_with(attr) do
validates_absence_of attr
end
end
def having_many(plural_name, options = {})
define_model plural_name.to_s.singularize
define_model :parent do
has_many plural_name
if options[:absence]
validates_absence_of plural_name
end
end.new
end
def having_and_belonging_to_many(plural_name, options = {})
create_table 'children_parents', id: false do |t|
t.integer "#{plural_name.to_s.singularize}_id"
t.integer :parent_id
end
define_model plural_name.to_s.singularize
define_model :parent do
has_and_belongs_to_many plural_name
if options[:absence]
validates_absence_of plural_name
end
end.new
end
end
end