From c22d7c89e0fe6ce940098401870212a58ba17a3f Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 23 Jan 2014 11:07:36 -0700 Subject: [PATCH] Extract examples in README to inline documentation --- README.md | 1389 +---------------- lib/shoulda/matchers/action_controller.rb | 20 - .../action_controller/callback_matcher.rb | 145 +- .../action_controller/filter_param_matcher.rb | 28 +- .../action_controller/redirect_to_matcher.rb | 43 +- .../render_template_matcher.rb | 48 +- .../render_with_layout_matcher.rb | 68 +- .../action_controller/rescue_from_matcher.rb | 31 + .../action_controller/respond_with_matcher.rb | 99 +- .../action_controller/route_matcher.rb | 112 +- .../action_controller/route_params.rb | 5 +- .../action_controller/set_session_matcher.rb | 75 +- .../set_the_flash_matcher.rb | 159 +- .../strong_parameters_matcher.rb | 146 ++ lib/shoulda/matchers/active_model.rb | 25 - .../allow_mass_assignment_of_matcher.rb | 75 +- .../active_model/allow_value_matcher.rb | 178 ++- .../active_model/disallow_value_matcher.rb | 8 +- .../ensure_exclusion_of_matcher.rb | 98 +- .../ensure_inclusion_of_matcher.rb | 228 ++- .../active_model/ensure_length_of_matcher.rb | 228 ++- lib/shoulda/matchers/active_model/errors.rb | 7 +- .../active_model/exception_message_finder.rb | 2 +- .../have_secure_password_matcher.rb | 35 +- lib/shoulda/matchers/active_model/helpers.rb | 7 +- .../active_model/numericality_matchers.rb | 9 + .../comparison_matcher.rb | 9 +- .../even_number_matcher.rb | 7 +- .../numeric_type_matcher.rb | 5 +- .../odd_number_matcher.rb | 7 +- .../only_integer_matcher.rb | 7 +- .../validate_absence_of_matcher.rb | 66 +- .../validate_acceptance_of_matcher.rb | 64 +- .../validate_confirmation_of_matcher.rb | 58 +- .../validate_numericality_of_matcher.rb | 294 +++- .../validate_presence_of_matcher.rb | 98 +- .../validate_uniqueness_of_matcher.rb | 198 ++- .../active_model/validation_matcher.rb | 7 +- .../active_model/validation_message_finder.rb | 3 +- lib/shoulda/matchers/active_record.rb | 13 +- .../accept_nested_attributes_for_matcher.rb | 104 +- .../active_record/association_matcher.rb | 790 +++++++++- .../active_record/association_matchers.rb | 9 + .../counter_cache_matcher.rb | 5 +- .../association_matchers/dependent_matcher.rb | 5 +- .../inverse_of_matcher.rb | 5 +- .../association_matchers/model_reflection.rb | 1 + .../association_matchers/model_reflector.rb | 5 +- .../association_matchers/option_verifier.rb | 5 +- .../association_matchers/order_matcher.rb | 5 +- .../association_matchers/source_matcher.rb | 5 +- .../association_matchers/through_matcher.rb | 5 +- .../active_record/have_db_column_matcher.rb | 92 +- .../active_record/have_db_index_matcher.rb | 79 +- .../have_readonly_attribute_matcher.rb | 28 +- .../active_record/serialize_matcher.rb | 95 +- lib/shoulda/matchers/assertion_error.rb | 3 + lib/shoulda/matchers/doublespeak.rb | 1 + lib/shoulda/matchers/doublespeak/double.rb | 1 + .../matchers/doublespeak/double_collection.rb | 1 + .../double_implementation_registry.rb | 1 + .../matchers/doublespeak/object_double.rb | 1 + .../doublespeak/proxy_implementation.rb | 1 + lib/shoulda/matchers/doublespeak/structs.rb | 2 + .../doublespeak/stub_implementation.rb | 1 + lib/shoulda/matchers/doublespeak/world.rb | 1 + lib/shoulda/matchers/error.rb | 1 + .../matchers/independent/delegate_matcher.rb | 124 +- .../delegate_matcher/stubbed_target.rb | 5 +- .../integrations/nunit_test_case_detection.rb | 3 + lib/shoulda/matchers/rails_shim.rb | 5 +- lib/shoulda/matchers/version.rb | 1 + lib/shoulda/matchers/warn.rb | 1 + 73 files changed, 3597 insertions(+), 1898 deletions(-) create mode 100644 lib/shoulda/matchers/active_model/numericality_matchers.rb create mode 100644 lib/shoulda/matchers/active_record/association_matchers.rb diff --git a/README.md b/README.md index 34a4eea92..64c519535 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,71 @@ shoulda-matchers provides Test::Unit- and RSpec-compatible one-liners that test common Rails functionality. These tests would otherwise be much longer, more complex, and error-prone. +### ActiveModel Matchers + +* **[allow_mass_assignment_of](Shoulda/Matchers/ActiveModel.html#allow_mass_assignment_of-instance_method)** + tests usage of Rails 3's `attr_accessible` and `attr_protected` macros. +* **[allow_value](Shoulda/Matchers/ActiveModel.html#allow_value-instance_method)** + tests usage of the `validates_format_of` validation. +* **[ensure_inclusion_of](Shoulda/Matchers/ActiveModel.html#ensure_inclusion_of-instance_method)** + tests usage of `validates_inclusion_of`. +* **[ensure_exclusion_of](Shoulda/Matchers/ActiveModel.html#ensure_exclusion_of-instance_method)** + tests usage of `validates_exclusion_of`. +* **[ensure_length_of](Shoulda/Matchers/ActiveModel.html#ensure_length_of-instance_method)** + tests usage of `validates_length_of`. +* **[have_secure_password](Shoulda/Matchers/ActiveModel.html#have_secure_password-instance_method)** + tests usage of `has_secure_password`. +* **[validate_confirmation_of](Shoulda/Matchers/ActiveModel.html#validate_confirmation_of-instance_method)** + tests usage of `validates_confirmation_of`. +* **[validate_numericality_of](Shoulda/Matchers/ActiveModel.html#validate_numericality_of-instance_method)** + tests usage of `validates_numericality_of`. +* **[validate_presence_of](Shoulda/Matchers/ActiveModel.html#validate_presence_of-instance_method)** + tests usage of `validates_presence_of`. +* **[validate_uniqueness_of](Shoulda/Matchers/ActiveModel.html#validate_uniqueness_of-instance_method)** + tests usage of `validates_uniqueness_of`. + +### ActiveRecord Matchers + +* **[accept_nested_attributes_for](Shoulda/Matchers/ActiveModel.html#accept_nested_attributes_for-instance_method)** + tests usage of the `accepts_nested_attributes_for` macro. +* **[belong_to](Shoulda/Matchers/ActiveModel.html#belong_to-instance_method)** + tests your `belongs_to` associations. +* **[have_many](Shoulda/Matchers/ActiveModel.html#have_many-instance_method)** + tests your `has_many` associations. +* **[have_one](Shoulda/Matchers/ActiveModel.html#have_one-instance_method)** + tests your `has_one` associations. +* **[have_and_belong_to_many](Shoulda/Matchers/ActiveModel.html#have_and_belong_to_many-instance_method)** + tests your `has_and_belongs_to_many` associations. +* **[have_db_column](Shoulda/Matchers/ActiveModel.html#have_db_column-instance_method)** + tests that the table that backs your model has a specific column. +* **[have_db_index](Shoulda/Matchers/ActiveModel.html#have_db_index-instance_method)** + tests that the table that backs your model has an index on a specific column. +* **[have_readonly_attribute](Shoulda/Matchers/ActiveModel.html#have_readonly_attribute-instance_method)** + tests usage of the `attr_readonly` macro. +* **[serialize](Shoulda/Matchers/ActiveModel.html#serialize-instance_method)** + tests usage of the `serialize` macro. + +### ActionController Matchers + +* **[filter_param](Shoulda/Matchers/ActiveModel.html#filter_param-instance_method)** + tests parameter filtering configuration. +* **[redirect_to](Shoulda/Matchers/ActiveModel.html#redirect_to-instance_method)** + tests that an action redirects to a certain location. +* **[render_template](Shoulda/Matchers/ActiveModel.html#render_template-instance_method)** + tests that an action renders a template. +* **[render_with_layout](Shoulda/Matchers/ActiveModel.html#render_with_layout-instance_method)** + tests that an action is rendereed with a certain layout. +* **[rescue_from](Shoulda/Matchers/ActiveModel.html#rescue_from-instance_method)** + tests usage of the `rescue_from` macro. +* **[respond_with](Shoulda/Matchers/ActiveModel.html#respond_with-instance_method)** + tests that an action responds with a certain status code. +* **[route](Shoulda/Matchers/ActiveModel.html#route-instance_method)** + tests your routes. +* **[set_session](Shoulda/Matchers/ActiveModel.html#set_session-instance_method)** + makes assertions on the `session` hash. +* **[set_the_flash](Shoulda/Matchers/ActiveModel.html#set_the_flash-instance_method)** + makes assertions on the `flash` hash. + ## Installation ### RSpec @@ -66,1330 +131,6 @@ gem 'shoulda-matchers' gem 'activemodel' ``` -## Usage - -Different matchers apply to different parts of Rails: - -* [ActiveModel](#activemodel-matchers) -* [ActiveRecord](#activerecord-matchers) -* [ActionController](#actioncontroller-matchers) - -### ActiveModel Matchers - -*Jump to: [allow_mass_assignment_of](#allow_mass_assignment_of), [allow_value](#allow_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 -ActiveModel model. - -#### allow_mass_assignment_of - -The `allow_mass_assignment_of` matcher tests usage of Rails 3's -`attr_accessible` and `attr_protected` macros, asserting that attributes can or -cannot be mass-assigned on a record. - -```ruby -class Post < ActiveRecord::Base - attr_accessible :title - attr_accessible :published_status, as: :admin -end - -class User < ActiveRecord::Base - attr_protected :encrypted_password -end - -# RSpec -describe Post do - it { should allow_mass_assignment_of(:title) } - it { should allow_mass_assignment_of(:published_status).as(:admin) } -end - -describe User do - it { should_not allow_mass_assignment_of(:encrypted_password) } -end - -# Test::Unit -class PostTest < ActiveSupport::TestCase - should allow_mass_assignment_of(:title) - should allow_mass_assignment_of(:published_status).as(:admin) -end - -class UserTest < ActiveSupport::TestCase - should_not allow_mass_assignment_of(:encrypted_password) -end -``` - -#### allow_value - -The `allow_value` matcher tests usage of the `validates_format_of` validation. -It asserts that an attribute can be set to one or more values, succeeding if -none of the values cause the record to be invalid. - -```ruby -class UserProfile < ActiveRecord::Base - validates_format_of :website_url, with: URI.regexp - - validates_format_of :birthday_as_string, - with: /^(\d+)-(\d+)-(\d+)$/, - on: :create - - validates_format_of :state, - with: /^(open|closed)$/, - message: 'State must be open or closed' -end - -# RSpec -describe UserProfile do - it { should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url) } - it { should_not allow_value('asdfjkl').for(:website_url) } - - it do - should allow_value('2013-01-01'). - for(:birthday_as_string). - on(:create) - end - - it do - should allow_value('open', 'closed'). - for(:state). - with_message('State must be open or closed') - end -end - -# Test::Unit -class UserProfileTest < ActiveSupport::TestCase - should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url) - should_not allow_value('asdfjkl').for(:website_url) - - should allow_value('2013-01-01'). - for(:birthday_as_string). - on(:create) - - should allow_value('open', 'closed'). - for(:state). - with_message('State must be open or closed') -end -``` - -**PLEASE NOTE:** Using `should_not` with `allow_value` completely negates the -assertion. This means that if multiple values are given to `allow_value`, the -matcher succeeds once it sees the *first* value that will cause the record to be -invalid: - -```ruby -describe User do - # 'b' and 'c' will not be tested - it { should_not allow_value('a', 'b', 'c').for(:website_url) } -end -``` - -#### ensure_inclusion_of - -The `ensure_inclusion_of` matcher tests usage of the `validates_inclusion_of` -validation, asserting that an attribute can take a set of values and cannot -take values outside of this set. - -```ruby -class Issue < ActiveRecord::Base - validates_inclusion_of :state, in: %w(open resolved unresolved) - validates_inclusion_of :priority, in: 1..5 - - validates_inclusion_of :severity, - in: %w(low medium high), - message: 'Severity must be low, medium, or high' -end - -# RSpec -describe Issue do - it { should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved)) } - it { should ensure_inclusion_of(:priority).in_range(1..5) } - - it do - should ensure_inclusion_of(:severity). - in_array(%w(low medium high)). - with_message('Severity must be low, medium, or high') - end -end - -# Test::Unit -class IssueTest < ActiveSupport::TestCase - should ensure_inclusion_of(:state).in_array(%w(open resolved unresolved)) - should ensure_inclusion_of(:priority).in_range(1..5) - - should ensure_inclusion_of(:severity). - in_array(%w(low medium high)). - with_message('Severity must be low, medium, or high') -end -``` - -#### ensure_exclusion_of - -The `ensure_exclusion_of` matcher tests usage of the `validates_exclusion_of` -validation, asserting that an attribute cannot take a set of values. - -```ruby -class Game < ActiveRecord::Base - validates_exclusion_of :supported_os, in: ['Mac', 'Linux'] - validates_exclusion_of :floors_with_enemies, in: 5..8 - - validates_exclusion_of :weapon, - in: ['pistol', 'paintball gun', 'stick'], - message: 'You chose a puny weapon' -end - -# RSpec -describe Game do - it { should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux']) } - it { should ensure_exclusion_of(:floors_with_enemies).in_range(5..8) } - - it do - should ensure_exclusion_of(:weapon). - in_array(['pistol', 'paintball gun', 'stick']). - with_message('You chose a puny weapon') - end -end - -# Test::Unit -class GameTest < ActiveSupport::TestCase - should ensure_exclusion_of(:supported_os).in_array(['Mac', 'Linux']) - should ensure_exclusion_of(:floors_with_enemies).in_range(5..8) - - should ensure_exclusion_of(:weapon). - in_array(['pistol', 'paintball gun', 'stick']). - with_message('You chose a puny weapon') -end -``` - -#### ensure_length_of - -The `ensure_length_of` matcher tests usage of the `validates_length_of` matcher. - -```ruby -class User < ActiveRecord::Base - validates_length_of :bio, minimum: 15 - validates_length_of :favorite_superhero, is: 6 - validates_length_of :status_update, maximum: 140 - validates_length_of :password, in: 5..30 - - validates_length_of :api_token, - in: 10..20, - message: 'API token must be in between 10 and 20 characters' - - validates_length_of :secret_key, in: 15..100, - too_short: 'Secret key must be more than 15 characters', - too_long: 'Secret key cannot be more than 100 characters' -end - -# RSpec -describe User do - it { should ensure_length_of(:bio).is_at_least(15) } - it { should ensure_length_of(:favorite_superhero).is_equal_to(6) } - it { should ensure_length_of(:status_update).is_at_most(140) } - it { should ensure_length_of(:password).is_at_least(5).is_at_most(30) } - - it do - should ensure_length_of(:api_token). - is_at_least(10). - is_at_most(20). - with_message('Password must be in between 10 and 20 characters') - end - - it do - should ensure_length_of(:secret_key). - is_at_least(15). - is_at_most(100). - with_short_message('Secret key must be more than 15 characters'). - with_long_message('Secret key cannot be more than 100 characters') - end -end - -# Test::Unit -class UserTest < ActiveSupport::TestCase - should ensure_length_of(:bio).is_at_least(15) - should ensure_length_of(:favorite_superhero).is_equal_to(6) - should ensure_length_of(:status_update).is_at_most(140) - should ensure_length_of(:password).is_at_least(5).is_at_most(30) - - should ensure_length_of(:api_token). - is_at_least(15). - is_at_most(20). - with_message('Password must be in between 15 and 20 characters') - - should ensure_length_of(:secret_key). - is_at_least(15). - is_at_most(100). - with_short_message('Secret key must be more than 15 characters'). - with_long_message('Secret key cannot be more than 100 characters') -end -``` - -#### have_secure_password - -The `have_secure_password` matcher tests usage of the `has_secure_password` -macro. - -```ruby -class User < ActiveRecord::Base - has_secure_password -end - -# RSpec -describe User do - it { should have_secure_password } -end - -# Test::Unit -class UserTest < ActiveSupport::TestCase - should have_secure_password -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 -`validates_acceptance_of` validation. - -```ruby -class Registration < ActiveRecord::Base - validates_acceptance_of :eula - validates_acceptance_of :terms_of_service, - message: 'You must accept the terms of service' -end - -# RSpec -describe Registration do - it { should validate_acceptance_of(:eula) } - - it do - should validate_acceptance_of(:terms_of_service). - with_message('You must accept the terms of service') - end -end - -# Test::Unit -class RegistrationTest < ActiveSupport::TestCase - should validate_acceptance_of(:eula) - - should validate_acceptance_of(:terms_of_service). - with_message('You must accept the terms of service') -end -``` - -#### validate_confirmation_of - -The `validate_confirmation_of` matcher tests usage of the -`validates_confirmation_of` validation. - -```ruby -class User < ActiveRecord::Base - validates_confirmation_of :email - validates_confirmation_of :password, message: 'Please re-enter your password' -end - -# RSpec -describe User do - it do - should validate_confirmation_of(:email) - end - - it do - should validate_confirmation_of(:password). - with_message('Please re-enter your password') - end -end - -# Test::Unit -class UserTest < ActiveSupport::TestCase - should validate_confirmation_of(:email) - - should validate_confirmation_of(:password). - with_message('Please re-enter your password') -end -``` - -#### validate_numericality_of - -The `validate_numericality_of` matcher tests usage of the -`validates_numericality_of` validation. - -```ruby -class Person < ActiveRecord::Base - validates_numericality_of :gpa - validates_numericality_of :age, only_integer: true - validates_numericality_of :legal_age, greater_than: 21 - validates_numericality_of :height, greater_than_or_equal_to: 55 - validates_numericality_of :weight, equal_to: 150 - validates_numericality_of :number_of_cars, less_than: 2 - validates_numericality_of :birth_year, less_than_or_equal_to: 1987 - validates_numericality_of :birth_day, odd: true - validates_numericality_of :birth_month, even: true - validates_numericality_of :rank, less_than_or_equal_to: 10, allow_nil: true - - validates_numericality_of :number_of_dependents, - message: 'Number of dependents must be a number' -end - -# RSpec -describe Person do - it { should validate_numericality_of(:gpa) } - it { should validate_numericality_of(:age).only_integer } - it { should validate_numericality_of(:legal_age).is_greater_than(21) } - it { should validate_numericality_of(:height).is_greater_than_or_equal_to(55) } - it { should validate_numericality_of(:weight).is_equal_to(150) } - it { should validate_numericality_of(:number_of_cars).is_less_than(2) } - it { should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987) } - it { should validate_numericality_of(:birth_day).odd } - it { should validate_numericality_of(:birth_month).even } - - it do - should validate_numericality_of(:number_of_dependents). - with_message('Number of dependents must be a number') - end -end - -# Test::Unit -class PersonTest < ActiveSupport::TestCase - should validate_numericality_of(:gpa) - should validate_numericality_of(:age).only_integer - should validate_numericality_of(:legal_age).is_greater_than(21) - should validate_numericality_of(:height).is_greater_than_or_equal_to(55) - should validate_numericality_of(:weight).is_equal_to(150) - should validate_numericality_of(:number_of_cars).is_less_than(2) - should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987) - should validate_numericality_of(:birth_day).odd - should validate_numericality_of(:birth_month).even - - should validate_numericality_of(:number_of_dependents). - with_message('Number of dependents must be a number') -end -``` - -#### validate_presence_of - -The `validate_presence_of` matcher tests usage of the `validates_presence_of` -matcher. - -```ruby -class Robot < ActiveRecord::Base - validates_presence_of :arms - validates_presence_of :legs, message: 'Robot has no legs' -end - -# RSpec -describe Robot do - it { should validate_presence_of(:arms) } - it { should validate_presence_of(:legs).with_message('Robot has no legs') } -end - -# Test::Unit -class RobotTest < ActiveSupport::TestCase - should validate_presence_of(:arms) - should validate_presence_of(:legs).with_message('Robot has no legs') -end -``` - -#### validate_uniqueness_of - -The `validate_uniqueness_of` matcher tests usage of the -`validates_uniqueness_of` validation. - -```ruby -class Post < ActiveRecord::Base - validates_uniqueness_of :permalink - validates_uniqueness_of :slug, scope: :user_id - validates_uniqueness_of :key, case_insensitive: true - validates_uniqueness_of :author_id, allow_nil: true - - validates_uniqueness_of :title, message: 'Please choose another title' -end - -# RSpec -describe Post do - it { should validate_uniqueness_of(:permalink) } - it { should validate_uniqueness_of(:slug).scoped_to(:user_id) } - it { should validate_uniqueness_of(:key).case_insensitive } - it { should validate_uniqueness_of(:author_id).allow_nil } - - it do - should validate_uniqueness_of(:title). - with_message('Please choose another title') - end -end - -# Test::Unit -class PostTest < ActiveSupport::TestCase - should validate_uniqueness_of(:permalink) - should validate_uniqueness_of(:slug).scoped_to(:user_id) - should validate_uniqueness_of(:key).case_insensitive - should validate_uniqueness_of(:author_id).allow_nil - - should validate_uniqueness_of(:title). - with_message('Please choose another title') -end -``` - -**PLEASE NOTE:** This matcher works differently from other validation matchers. -Since the very concept of uniqueness depends on checking against a pre-existing -record in the database, this matcher will first use the model you're testing to -query for such a record, and if it can't find an existing one, it will create -one itself. Sometimes this step fails, especially if you have other validations -on the attribute you're testing (or, if you have database-level restrictions on -any attributes). In this case, the solution is to create a record before you use -`validate_uniqueness_of`. - -For example, if you have this model: - -```ruby -class Post < ActiveRecord::Base - validates_presence_of :permalink - validates_uniqueness_of :permalink -end -``` - -then you will need to test it like this: - -```ruby -describe Post do - it do - Post.create!(title: 'This is the title') - should validate_uniqueness_of(:permalink) - end -end -``` - -### ActiveRecord Matchers - -*Jump to: [accept_nested_attributes_for](#accept_nested_attributes_for), [belong_to](#belong_to), [have_many](#have_many), [have_one](#have_one), [have_and_belong_to_many](#have_and_belong_to_many), [have_db_column](#have_db_column), [have_db_index](#have_db_index), [have_readonly_attribute](#have_readonly_attribute), [serialize](#serialize)* - -#### accept_nested_attributes_for - -The `accept_nested_attributes_for` matcher tests usage of the -`accepts_nested_attributes_for` macro. - -```ruby -class Car < ActiveRecord::Base - accept_nested_attributes_for :doors - accept_nested_attributes_for :mirrors, allow_destroy: true - accept_nested_attributes_for :windows, limit: 3 - accept_nested_attributes_for :engine, update_only: true -end - -# RSpec -describe Car do - it { should accept_nested_attributes_for(:doors) } - it { should accept_nested_attributes_for(:mirrors).allow_destroy(true) } - it { should accept_nested_attributes_for(:windows).limit(3) } - it { should accept_nested_attributes_for(:engine).update_only(true) } -end - -# Test::Unit (using Shoulda) -class CarTest < ActiveSupport::TestCase - should accept_nested_attributes_for(:doors) - should accept_nested_attributes_for(:mirrors).allow_destroy(true) - should accept_nested_attributes_for(:windows).limit(3) - should accept_nested_attributes_for(:engine).update_only(true) -end -``` - -#### belong_to - -The `belong_to` matcher tests your `belongs_to` associations. - -```ruby -class Person < ActiveRecord::Base - belongs_to :organization - belongs_to :family, -> { where(everyone_is_perfect: false) } - belongs_to :previous_company, -> { order('hired_on desc') } - belongs_to :ancient_city, class_name: 'City' - belongs_to :great_country, foreign_key: 'country_id' - belongs_to :mental_institution, touch: true - belongs_to :world, dependent: :destroy -end - -# RSpec -describe Person do - it { should belong_to(:organization) } - it { should belong_to(:family).conditions(everyone_is_perfect: false) } - it { should belong_to(:previous_company).order('hired_on desc') } - it { should belong_to(:ancient_city).class_name('City') } - it { should belong_to(:great_country).with_foreign_key('country_id') } - it { should belong_to(:mental_institution).touch(true) } - it { should belong_to(:world).dependent(:destroy) } -end - -# Test::Unit -class PersonTest < ActiveSupport::TestCase - should belong_to(:organization) - should belong_to(:family).conditions(everyone_is_perfect: false) - should belong_to(:previous_company).order('hired_on desc') - should belong_to(:ancient_city).class_name('City') - should belong_to(:great_country).with_foreign_key('country_id') - should belong_to(:mental_institution).touch(true) - should belong_to(:world).dependent(:destroy) -end -``` - -#### have_many - -The `have_many` matcher tests your `has_many` and `has_many :through` associations. - -```ruby -class Person < ActiveRecord::Base - has_many :friends - has_many :acquaintances, through: :friends - has_many :job_offers, through: :friends, source: :opportunities - has_many :coins, -> { where(condition: 'mint') } - has_many :shirts, -> { order('color') } - has_many :hopes, class_name: 'Dream' - has_many :worries, foreign_key: 'worrier_id' - has_many :distractions, counter_cache: true - has_many :ideas, validate: false - has_many :topics_of_interest, touch: true - has_many :secret_documents, dependent: :destroy -end - -# RSpec -describe Person do - it { should have_many(:friends) } - it { should have_many(:acquaintances).through(:friends) } - it { should have_many(:job_offers).through(:friends).source(:opportunities) } - it { should have_many(:coins).conditions(condition: 'mint') } - it { should have_many(:shirts).order('color') } - it { should have_many(:hopes).class_name('Dream') } - it { should have_many(:worries).with_foreign_key('worrier_id') } - it { should have_many(:ideas).validate(false) } - it { should have_many(:distractions).counter_cache(true) } - it { should have_many(:topics_of_interest).touch(true) } - it { should have_many(:secret_documents).dependent(:destroy) } -end - -# Test::Unit -class PersonTest < ActiveSupport::TestCase - should have_many(:friends) - should have_many(:acquaintances).through(:friends) - should have_many(:job_offers).through(:friends).source(:opportunities) - should have_many(:coins).conditions(condition: 'mint') - should have_many(:shirts).order('color') - should have_many(:hopes).class_name('Dream') - should have_many(:worries).with_foreign_key('worrier_id') - should have_many(:ideas).validate(false) - should have_many(:distractions).counter_cache(true) - should have_many(:topics_of_interest).touch(true) - should have_many(:secret_documents).dependent(:destroy) -end -``` - -#### have_one - -The `have_one` matcher tests your `has_one` and `has_one :through` associations. - -```ruby -class Person < ActiveRecord::Base - has_one :partner - has_one :life, through: :partner - has_one :car, through: :partner, source: :vehicle - has_one :pet, -> { where('weight < 80') } - has_one :focus, -> { order('priority desc') } - has_one :chance, class_name: 'Opportunity' - has_one :job, foreign_key: 'worker_id' - has_one :parking_card, validate: false - has_one :contract, dependent: :nullify -end - -# RSpec -describe Person do - it { should have_one(:partner) } - it { should have_one(:life).through(:partner) } - it { should have_one(:car).through(:partner).source(:vehicle) } - it { should have_one(:pet).conditions('weight < 80') } - it { should have_one(:focus).order('priority desc') } - it { should have_one(:chance).class_name('Opportunity') } - it { should have_one(:job).with_foreign_key('worker_id') } - it { should have_one(:parking_card).validate(false) } - it { should have_one(:contract).dependent(:nullify) } -end - -# Test::Unit -class PersonTest < ActiveSupport::TestCase - should have_one(:partner) - should have_one(:life).through(:partner) - should have_one(:car).through(:partner).source(:vehicle) - should have_one(:pet).conditions('weight < 80') - should have_one(:focus).order('priority desc') - should have_one(:chance).class_name('Opportunity') - should have_one(:job).with_foreign_key('worker_id') - should have_one(:parking_card).validate(false) - should have_one(:contract).dependent(:nullify) -end -``` - -#### have_and_belong_to_many - -The `have_and_belong_to_many` matcher tests your `has_and_belongs_to_many` -associations. - -```ruby -class Person < ActiveRecord::Base - has_and_belongs_to_many :awards - has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') } - has_and_belongs_to_many :projects, -> { order('time_spent') } - has_and_belongs_to_many :places_visited, class_name: 'City' - has_and_belongs_to_many :interviews, validate: false -end - -# RSpec -describe Person do - it { should have_and_belong_to_many(:awards) } - it { should have_and_belong_to_many(:issues).conditions(difficulty: 'hard') } - it { should have_and_belong_to_many(:projects).order('time_spent') } - it { should have_and_belong_to_many(:places_visited).class_name('City') } - it { should have_and_belong_to_many(:interviews).validate(false) } -end - -# Test::Unit -class PersonTest < ActiveSupport::TestCase - should have_and_belong_to_many(:awards) - should have_and_belong_to_many(:issues).conditions(difficulty: 'hard') - should have_and_belong_to_many(:projects).order('time_spent') - should have_and_belong_to_many(:places_visited).class_name('City') - should have_and_belong_to_many(:interviews).validate(false) -end -``` - -#### have_db_column - -The `have_db_column` matcher tests that the table that backs your model -has a specific column. - -```ruby -class CreatePhones < ActiveRecord::Migration - def change - create_table :phones do |t| - t.decimal :supported_ios_version - t.string :model, null: false - t.decimal :camera_aperture, precision: 1 - end - end -end - -# RSpec -describe Phone do - it { should have_db_column(:supported_ios_version) } - it { should have_db_column(:model).with_options(null: false) } - - it do - should have_db_column(:camera_aperture). - of_type(:decimal). - with_options(precision: 1) - end -end - -# Test::Unit -class PhoneTest < ActiveSupport::TestCase - should have_db_column(:supported_ios_version) - should have_db_column(:model).with_options(null: false) - - should have_db_column(:camera_aperture). - of_type(:decimal). - with_options(precision: 1) -end -``` - -#### have_db_index - -The `have_db_index` matcher tests that the table that backs your model has a -index on a specific column. - -```ruby -class CreateBlogs < ActiveRecord::Migration - def change - create_table :blogs do |t| - t.integer :user_id, null: false - t.string :name, null: false - end - - add_index :blogs, :user_id - add_index :blogs, :name, unique: true - end -end - -# RSpec -describe Blog do - it { should have_db_index(:user_id) } - it { should have_db_index(:name).unique(true) } -end - -# Test::Unit -class BlogTest < ActiveSupport::TestCase - should have_db_index(:user_id) - should have_db_index(:name).unique(true) -end -``` - -#### have_readonly_attribute - -The `have_readonly_attribute` matcher tests usage of the `attr_readonly` macro. - -```ruby -class User < ActiveRecord::Base - attr_readonly :password -end - -# RSpec -describe User do - it { should have_readonly_attribute(:password) } -end - -# Test::Unit -class UserTest < ActiveSupport::TestCase - should have_readonly_attribute(:password) -end -``` - -#### serialize - -The `serialize` matcher tests usage of the `serialize` macro. - -```ruby -class ProductOptionsSerializer - def load(string) - # ... - end - - def dump(options) - # ... - end -end - -class Product < ActiveRecord::Base - serialize :customizations - serialize :specifications, ProductSpecsSerializer - serialize :options, ProductOptionsSerializer.new -end - -# RSpec -describe Product do - it { should serialize(:customizations) } - it { should serialize(:specifications).as(ProductSpecsSerializer) } - it { should serialize(:options).as_instance_of(ProductOptionsSerializer) } -end - -# Test::Unit -class ProductTest < ActiveSupport::TestCase - should serialize(:customizations) - should serialize(:specifications).as(ProductSpecsSerializer) - should serialize(:options).as_instance_of(ProductOptionsSerializer) -end -``` - -### ActionController Matchers - -*Jump to: [filter_param](#filter_param), [permit](#permit), [redirect_to](#redirect_to), [render_template](#render_template), [render_with_layout](#render_with_layout), [rescue_from](#rescue_from), [respond_with](#respond_with), [route](#route), [set_session](#set_session), [set_the_flash](#set_the_flash), [use_after_filter / use_after_action](#use_after_filter--use_after_action), [use_around_filter / use_around_action](#use_around_filter--use_around_action), [use_before_filter / use_around_action](#use_before_filter--use_before_action)* - -#### filter_param - -The `filter_param` matcher tests parameter filtering configuration. - -```ruby -class MyApplication < Rails::Application - config.filter_parameters << :secret_key -end - -# RSpec -describe ApplicationController do - it { should filter_param(:secret_key) } -end - -# Test::Unit -class ApplicationControllerTest < ActionController::TestCase - should filter_param(:secret_key) -end -``` - -#### permit - -The `permit` matcher tests that only whitelisted parameters are permitted. - -```ruby -class UserController < ActionController::Base - def create - User.create(user_params) - end - - private - - def user_params - params.require(:user).permit(:email) - end -end - -# RSpec -describe UserController do - it { should permit(:email).for(:create) } -end - -# Test::Unit -class UserControllerTest < ActionController::TestCase - should permit(:email).for(:create) -end -``` - -#### redirect_to - -The `redirect_to` matcher tests that an action redirects to a certain location. -In a test suite using RSpec, it is very similar to rspec-rails's `redirect_to` -matcher. In a test suite using Test::Unit / Shoulda, it provides a more -expressive syntax over `assert_redirected_to`. - -```ruby -class PostsController < ApplicationController - def show - redirect_to :index - end -end - -# RSpec -describe PostsController do - describe 'GET #list' do - before { get :list } - - it { should redirect_to(posts_path) } - end -end - -# Test::Unit -class PostsControllerTest < ActionController::TestCase - context 'GET #list' do - setup { get :list } - - should redirect_to { posts_path } - end -end -``` - -#### render_template - -The `render_template` matcher tests that an action renders a template. -In RSpec, it is very similar to rspec-rails's `render_template` matcher. -In Test::Unit, it provides a more expressive syntax over `assert_template`. - -```ruby -class PostsController < ApplicationController - def show - end -end - -# RSpec -describe PostsController do - describe 'GET #show' do - before { get :show } - - it { should render_template('show') } - end -end - -# Test::Unit -class PostsControllerTest < ActionController::TestCase - context 'GET #show' do - setup { get :show } - - should render_template('show') - end -end -``` - -#### render_with_layout - -The `render_with_layout` matcher tests that an action is rendered with a certain -layout. - -```ruby -class PostsController < ApplicationController - def show - render layout: 'posts' - end -end - -# RSpec -describe PostsController do - describe 'GET #show' do - before { get :show } - - it { should render_with_layout('posts') } - end -end - -# Test::Unit -class PostsControllerTest < ActionController::TestCase - context 'GET #show' do - setup { get :show } - - should render_with_layout('posts') - end -end -``` - -#### rescue_from - -The `rescue_from` matcher tests usage of the `rescue_from` macro. - -```ruby -class ApplicationController < ActionController::Base - rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found - - private - - def handle_not_found - # ... - end -end - -# RSpec -describe ApplicationController do - it do - should rescue_from(ActiveRecord::RecordNotFound). - with(:handle_not_found) - end -end - -# Test::Unit -class ApplicationControllerTest < ActionController::TestCase - should rescue_from(ActiveRecord::RecordNotFound). - with(:handle_not_found) -end -``` - -#### respond_with - -The `respond_with` matcher tests that an action responds with a certain status -code. - -```ruby -class PostsController < ApplicationController - def index - render status: 403 - end - - def show - render status: :locked - end - - def destroy - render status: 508 - end -end - -# RSpec -describe PostsController do - describe 'GET #index' do - before { get :index } - - it { should respond_with(403) } - end - - describe 'GET #show' do - before { get :show } - - it { should respond_with(:locked) } - end - - describe 'DELETE #destroy' do - before { delete :destroy } - - it { should respond_with(500..600) } - end -end - -# Test::Unit -class PostsControllerTest < ActionController::TestCase - context 'GET #index' do - setup { get :index } - - should respond_with(403) - end - - context 'GET #show' do - setup { get :show } - - should respond_with(:locked) - end - - context 'DELETE #destroy' do - setup { delete :destroy } - - should respond_with(500..600) - end -end -``` - -#### route - -The `route` matcher tests that a route resolves to a controller, action, and -params; and that the controller, action, and params generates the same route. For -an RSpec suite, this is like using a combination of `route_to` and -`be_routable`. For a Test::Unit suite, it provides a more expressive syntax -over `assert_routing`. - -```ruby -My::Application.routes.draw do - get '/posts', controller: 'posts', action: 'index' - get '/posts/:id' => 'posts#show' -end - -# RSpec -describe 'Routing' do - it { should route(:get, '/posts').to(controller: 'posts', action: 'index') } - it { should route(:get, '/posts/1').to('posts#show', id: 1) } -end - -# Test::Unit -class RoutesTest < ActionController::IntegrationTest - should route(:get, '/posts').to(controller: 'posts', action: 'index') - should route(:get, '/posts/1').to('posts#show', id: 1) -end -``` - -#### set_session - -The `set_session` matcher asserts that a key in the `session` hash has been set -to a certain value. - -```ruby -class PostsController < ApplicationController - def show - session[:foo] = 'bar' - end -end - -# RSpec -describe PostsController do - describe 'GET #show' do - before { get :show } - - it { should set_session(:foo).to('bar') } - it { should_not set_session(:baz) } - end -end - -# Test::Unit -class PostsControllerTest < ActionController::TestCase - context 'GET #show' do - setup { get :show } - - should set_session(:foo).to('bar') - should_not set_session(:baz) - end -end -``` - -#### set_the_flash - -The `set_the_flash` matcher asserts that a key in the `flash` hash is set to a -certain value. - -```ruby -class PostsController < ApplicationController - def index - flash[:foo] = 'A candy bar' - end - - def show - flash.now[:foo] = 'bar' - end - - def destroy - end -end - -# RSpec -describe PostsController do - describe 'GET #index' do - before { get :index } - - it { should set_the_flash.to('bar') } - it { should set_the_flash.to(/bar/) } - it { should set_the_flash[:foo].to('bar') } - it { should_not set_the_flash[:baz] } - end - - describe 'GET #show' do - before { get :show } - - it { should set_the_flash.now } - it { should set_the_flash[:foo].now } - it { should set_the_flash[:foo].to('bar').now } - end - - describe 'DELETE #destroy' do - before { delete :destroy } - - it { should_not set_the_flash } - end -end - -# Test::Unit -class PostsControllerTest < ActionController::TestCase - context 'GET #index' do - setup { get :index } - - should set_the_flash.to('bar') - should set_the_flash.to(/bar/) - should set_the_flash[:foo].to('bar') - should_not set_the_flash[:baz] - end - - context 'GET #show' do - setup { get :show } - - should set_the_flash.now - should set_the_flash[:foo].now - should set_the_flash[:foo].to('bar').now - end - - context 'DELETE #destroy' do - setup { delete :destroy } - - should_not set_the_flash - end -end -``` - -#### use_after_filter / use_after_action - -The `use_after_filter` ensures a given `after_filter` is used. This is also -available as `use_after_action` to provide Rails 4 support. - -```ruby -class UserController < ActionController::Base - after_filter :log_activity -end - -# RSpec -describe UserController do - it { should use_after_filter(:log_activity) } -end - -# Test::Unit -class UserControllerTest < ActionController::TestCase - should use_after_filter(:log_activity) -end -``` - -#### use_around_filter / use_around_action - -The `use_around_filter` ensures a given `around_filter` is used. This is also -available as `use_around_action` to provide Rails 4 support. - -```ruby -class UserController < ActionController::Base - around_filter :log_activity -end - -# RSpec -describe UserController do - it { should use_around_filter(:log_activity) } -end - -# Test::Unit -class UserControllerTest < ActionController::TestCase - should use_around_filter(:log_activity) -end -``` - -#### use_before_filter / use_before_action - -The `use_before_filter` ensures a given `before_filter` is used. This is also -available as `use_before_action` for Rails 4 support. - -```ruby -class UserController < ActionController::Base - before_filter :authenticate_user! -end - -# RSpec -describe UserController do - it { should use_before_filter(:authenticate_user!) } -end - -# Test::Unit -class UserControllerTest < ActionController::TestCase - should use_before_filter(:authenticate_user!) -end -``` - -## Independent Matchers - -Matchers to test non-Rails-dependent code: - -#### delegate_method - -```ruby -class Human < ActiveRecord::Base - has_one :robot - delegate :work, to: :robot - - # alternatively, if you are not using Rails - def work - robot.work - end - - def protect - robot.protect('Sarah Connor') - end - - def speak - robot.beep_boop - end -end - -# RSpec -describe Human do - it { should delegate_method(:work).to(:robot) } - it { should delegate_method(:protect).to(:robot).with_arguments('Sarah Connor') } - it { should delegate_method(:beep_boop).to(:robot).as(:speak) } -end - -# Test::Unit -class HumanTest < ActiveSupport::TestCase - should delegate_method(:work).to(:robot) - should delegate_method(:protect).to(:robot).with_arguments('Sarah Connor') - should delegate_method(:beep_boop).to(:robot).as(:speak) -end -``` - ## Versioning shoulda-matchers follows Semantic Versioning 2.0 as defined at diff --git a/lib/shoulda/matchers/action_controller.rb b/lib/shoulda/matchers/action_controller.rb index 39e95550d..d5cf7d12e 100644 --- a/lib/shoulda/matchers/action_controller.rb +++ b/lib/shoulda/matchers/action_controller.rb @@ -13,26 +13,6 @@ module Shoulda module Matchers - # By using the matchers you can quickly and easily create concise and - # easy to read test suites. - # - # This code segment: - # - # describe UsersController, 'on GET to show with a valid id' do - # before(:each) do - # get :show, id: User.first.to_param - # end - # - # it { should respond_with(:success) } - # it { should render_template(:show) } - # it { should not_set_the_flash) } - # - # it 'does something else really cool' do - # expect(assigns[:user].id).to eq 1 - # end - # end - # - # Would produce 5 tests for the show action module ActionController end end diff --git a/lib/shoulda/matchers/action_controller/callback_matcher.rb b/lib/shoulda/matchers/action_controller/callback_matcher.rb index 637a1c5ff..a6928297b 100644 --- a/lib/shoulda/matchers/action_controller/callback_matcher.rb +++ b/lib/shoulda/matchers/action_controller/callback_matcher.rb @@ -1,67 +1,158 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - # Ensure a controller uses a given before_filter + module ActionController + # The `use_before_filter` matcher is used to test that a before_filter + # callback is defined within your controller. # - # Example: + # class UsersController < ApplicationController + # before_filter :authenticate_user! + # end + # + # # RSpec + # describe UsersController do + # it { should use_before_filter(:authenticate_user!) } + # it { should_not use_before_filter(:prevent_ssl) } + # end + # + # # Test::Unit + # class UsersControllerTest < ActionController::TestCase + # should use_before_filter(:authenticate_user!) + # should_not use_before_filter(:prevent_ssl) + # end + # + # @return [CallbackMatcher] # - # it { should use_before_filter(:authenticate_user!) } - # it { should_not use_before_filter(:prevent_ssl) } def use_before_filter(callback) CallbackMatcher.new(callback, :before, :filter) end - # Ensure a controller uses a given before_filter + # The `use_after_filter` matcher is used to test that an after_filter + # callback is defined within your controller. + # + # class IssuesController < ApplicationController + # after_filter :log_activity + # end + # + # # RSpec + # describe IssuesController do + # it { should use_after_filter(:log_activity) } + # it { should_not use_after_filter(:destroy_user) } + # end # - # Example: + # # Test::Unit + # class IssuesControllerTest < ActionController::TestCase + # should use_after_filter(:log_activity) + # should_not use_after_filter(:destroy_user) + # end + # + # @return [CallbackMatcher] # - # it { should use_after_filter(:log_activity) } - # it { should_not use_after_filter(:destroy_user) } def use_after_filter(callback) CallbackMatcher.new(callback, :after, :filter) end - # Ensure a controller uses a given before_action + # The `use_before_action` matcher is used to test that a before_action + # callback is defined within your controller. + # + # class UsersController < ApplicationController + # before_action :authenticate_user! + # end + # + # # RSpec + # describe UsersController do + # it { should use_before_action(:authenticate_user!) } + # it { should_not use_before_action(:prevent_ssl) } + # end + # + # # Test::Unit + # class UsersControllerTest < ActionController::TestCase + # should use_before_action(:authenticate_user!) + # should_not use_before_action(:prevent_ssl) + # end # - # Example: + # @return [CallbackMatcher] # - # it { should use_before_action(:authenticate_user!) } - # it { should_not use_before_action(:prevent_ssl) } def use_before_action(callback) CallbackMatcher.new(callback, :before, :action) end - # Ensure a controller uses a given after_action + # The `use_after_action` matcher is used to test that an after_action + # callback is defined within your controller. # - # Example: + # class IssuesController < ApplicationController + # after_action :log_activity + # end + # + # # RSpec + # describe IssuesController do + # it { should use_after_action(:log_activity) } + # it { should_not use_after_action(:destroy_user) } + # end + # + # # Test::Unit + # class IssuesControllerTest < ActionController::TestCase + # should use_after_action(:log_activity) + # should_not use_after_action(:destroy_user) + # end + # + # @return [CallbackMatcher] # - # it { should use_after_action(:log_activity) } - # it { should_not use_after_action(:destroy_user) } def use_after_action(callback) CallbackMatcher.new(callback, :after, :action) end - # Ensure a controller uses a given around_filter + # The `use_around_filter` matcher is used to test that an around_filter + # callback is defined within your controller. + # + # class ChangesController < ApplicationController + # around_filter :wrap_in_transaction + # end + # + # # RSpec + # describe ChangesController do + # it { should use_around_filter(:wrap_in_transaction) } + # it { should_not use_around_filter(:save_view_context) } + # end # - # Example: + # # Test::Unit + # class ChangesControllerTest < ActionController::TestCase + # should use_around_filter(:wrap_in_transaction) + # should_not use_around_filter(:save_view_context) + # end + # + # @return [CallbackMatcher] # - # it { should use_around_filter(:log_activity) } - # it { should_not use_around_filter(:destroy_user) } def use_around_filter(callback) CallbackMatcher.new(callback, :around, :filter) end - # Ensure a controller uses a given around_action + # The `use_around_action` matcher is used to test that an around_action + # callback is defined within your controller. + # + # class ChangesController < ApplicationController + # around_action :wrap_in_transaction + # end + # + # # RSpec + # describe ChangesController do + # it { should use_around_action(:wrap_in_transaction) } + # it { should_not use_around_action(:save_view_context) } + # end + # + # # Test::Unit + # class ChangesControllerTest < ActionController::TestCase + # should use_around_action(:wrap_in_transaction) + # should_not use_around_action(:save_view_context) + # end # - # Example: + # @return [CallbackMatcher] # - # it { should use_around_action(:log_activity) } - # it { should_not use_around_action(:destroy_user) } def use_around_action(callback) CallbackMatcher.new(callback, :around, :action) end - class CallbackMatcher # :nodoc: + # @private + class CallbackMatcher def initialize(method_name, kind, callback_type) @method_name = method_name @kind = kind diff --git a/lib/shoulda/matchers/action_controller/filter_param_matcher.rb b/lib/shoulda/matchers/action_controller/filter_param_matcher.rb index 531ec83d7..652f5bcf5 100644 --- a/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +++ b/lib/shoulda/matchers/action_controller/filter_param_matcher.rb @@ -1,16 +1,32 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - # Ensures that filter_parameter_logging is set for the specified key. + module ActionController + # The `filter_param` matcher is used to test parameter filtering + # configuration. Specifically, it asserts that the given parameter is + # present in `config.filter_parameters`. # - # Example: + # class MyApplication < Rails::Application + # config.filter_parameters << :secret_key + # end + # + # # RSpec + # describe ApplicationController do + # it { should filter_param(:secret_key) } + # end + # + # # Test::Unit + # class ApplicationControllerTest < ActionController::TestCase + # should filter_param(:secret_key) + # end + # + # @return [FilterParamMatcher] # - # it { should filter_param(:password) } def filter_param(key) FilterParamMatcher.new(key) end - class FilterParamMatcher # :nodoc: + # @private + class FilterParamMatcher def initialize(key) @key = key.to_s end diff --git a/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb b/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb index c5a959cb6..877afcd7d 100644 --- a/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +++ b/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb @@ -1,17 +1,46 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - # Ensures a controller redirected to the given url. + module ActionController + # The `redirect_to` matcher tests that an action redirects to a certain + # location. In a test suite using RSpec, it is very similar to + # rspec-rails's `redirect_to` matcher. In a test suite using Test::Unit / + # Shoulda, it provides a more expressive syntax over + # `assert_redirected_to`. # - # Example: + # class PostsController < ApplicationController + # def show + # redirect_to :index + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } + # + # it { should redirect_to(posts_path) } + # it { should redirect_to(action: :index) } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #show' do + # setup { get :show } + # + # should redirect_to { posts_path } + # should redirect_to(action: :index) + # end + # end + # + # @return [RedirectToMatcher] # - # it { should redirect_to('http://somewhere.com') } - # it { should redirect_to(users_path) } def redirect_to(url_or_description, &block) RedirectToMatcher.new(url_or_description, self, &block) end - class RedirectToMatcher # :nodoc: + # @private + class RedirectToMatcher attr_reader :failure_message, :failure_message_when_negated alias failure_message_for_should failure_message diff --git a/lib/shoulda/matchers/action_controller/render_template_matcher.rb b/lib/shoulda/matchers/action_controller/render_template_matcher.rb index a6024915e..ea60b5293 100644 --- a/lib/shoulda/matchers/action_controller/render_template_matcher.rb +++ b/lib/shoulda/matchers/action_controller/render_template_matcher.rb @@ -1,25 +1,49 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - # Ensures a controller rendered the given template. + module ActionController + # The `render_template` matcher tests that an action renders a template + # or partial. In RSpec, it is very similar to rspec-rails's + # `render_template` matcher. In Test::Unit, it provides a more expressive + # syntax over `assert_template`. # - # Example: + # class PostsController < ApplicationController + # def show + # end + # end # - # it { should render_template(:show) } + # # app/views/posts/show.html.erb + # <%= render 'sidebar' %> # - # assert that the "_customer" partial was rendered - # it { should render_template(partial: '_customer') } + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } # - # assert that the "_customer" partial was rendered twice - # it { should render_template(partial: '_customer', count: 2) } + # it { should render_template('show') } + # it { should render_template(partial: 'sidebar') } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #show' do + # setup { get :show } + # + # should render_template('show') + # should render_template(partial: 'sidebar') + # end + # end + # + # + # + # @return [RenderTemplateMatcher] # - # assert that no partials were rendered - # it { should render_template(partial: false) } def render_template(options = {}, message = nil) RenderTemplateMatcher.new(options, message, self) end - class RenderTemplateMatcher # :nodoc: + # @private + class RenderTemplateMatcher attr_reader :failure_message, :failure_message_when_negated alias failure_message_for_should failure_message diff --git a/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb b/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb index 322e81cc5..cfab58478 100644 --- a/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +++ b/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb @@ -1,20 +1,68 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - - # Ensures that the controller rendered with the given layout. + module ActionController + # The `render_with_layout` matcher asserts that an action is rendered with + # a particular layout. + # + # class PostsController < ApplicationController + # def show + # render layout: 'posts' + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } + # + # it { should render_with_layout('posts') } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #show' do + # setup { get :show } + # + # should render_with_layout('posts') + # end + # end + # + # It can also be used to assert that the action is not rendered with a + # layout at all: # - # Example: + # class PostsController < ApplicationController + # def sidebar + # render layout: false + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #sidebar' do + # before { get :sidebar } + # + # it { should_not render_with_layout } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #sidebar' do + # setup { get :sidebar } + # + # should_not render_with_layout + # end + # end + # + # @return [RenderWithLayoutMatcher] # - # it { should render_with_layout } - # it { should render_with_layout(:special) } - # it { should_not render_with_layout } def render_with_layout(expected_layout = nil) RenderWithLayoutMatcher.new(expected_layout).in_context(self) end - class RenderWithLayoutMatcher # :nodoc: - + # @private + class RenderWithLayoutMatcher def initialize(expected_layout) unless expected_layout.nil? @expected_layout = expected_layout.to_s diff --git a/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb b/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb index b36adf0fd..54b55b57f 100644 --- a/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +++ b/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb @@ -1,10 +1,41 @@ module Shoulda module Matchers module ActionController + # The `rescue_from` matcher tests usage of the `rescue_from` macro. It + # asserts that an exception and method are present in the list of + # exception handlers, and that the handler method exists. + # + # class ApplicationController < ActionController::Base + # rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found + # + # private + # + # def handle_not_found + # # ... + # end + # end + # + # # RSpec + # describe ApplicationController do + # it do + # should rescue_from(ActiveRecord::RecordNotFound). + # with(:handle_not_found) + # end + # end + # + # # Test::Unit + # class ApplicationControllerTest < ActionController::TestCase + # should rescue_from(ActiveRecord::RecordNotFound). + # with(:handle_not_found) + # end + # + # @return [RescueFromMatcher] + # def rescue_from(exception) RescueFromMatcher.new exception end + # @private class RescueFromMatcher def initialize(exception) @exception = exception diff --git a/lib/shoulda/matchers/action_controller/respond_with_matcher.rb b/lib/shoulda/matchers/action_controller/respond_with_matcher.rb index be48cfbe2..92d9e5c8d 100644 --- a/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +++ b/lib/shoulda/matchers/action_controller/respond_with_matcher.rb @@ -1,26 +1,95 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - - # Ensures a controller responded with expected 'response' status code. + module ActionController + # The `respond_with` matcher tests that an action responds with a certain + # status code. + # + # You can specify that the status should be a number: + # + # class PostsController < ApplicationController + # def index + # render status: 403 + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #index' do + # before { get :index } + # + # it { should respond_with(403) } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #index' do + # setup { get :index } + # + # should respond_with(403) + # end + # end + # + # You can specify that the status should be within a range of numbers: + # + # class PostsController < ApplicationController + # def destroy + # render status: 508 + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'DELETE #destroy' do + # before { delete :destroy } # - # You can pass an explicit status number like 200, 301, 404, 500 - # or its symbolic equivalent :success, :redirect, :missing, :error. - # See ActionController::StatusCodes for a full list. + # it { should respond_with(500..600) } + # end + # end # - # Example: + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'DELETE #destroy' do + # setup { delete :destroy } + # + # should respond_with(500..600) + # end + # end + # + # Finally, you can specify that the status should be a symbol: + # + # class PostsController < ApplicationController + # def show + # render status: :locked + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } + # + # it { should respond_with(:locked) } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #show' do + # setup { get :show } + # + # should respond_with(:locked) + # end + # end + # + # @return [RespondWithMatcher] # - # it { should respond_with(:success) } - # it { should respond_with(:redirect) } - # it { should respond_with(:missing) } - # it { should respond_with(:error) } - # it { should respond_with(501) } def respond_with(status) RespondWithMatcher.new(status) end - class RespondWithMatcher # :nodoc: - + # @private + class RespondWithMatcher def initialize(status) @status = symbol_to_status_code(status) end diff --git a/lib/shoulda/matchers/action_controller/route_matcher.rb b/lib/shoulda/matchers/action_controller/route_matcher.rb index 40495fbad..cb6f96cf2 100644 --- a/lib/shoulda/matchers/action_controller/route_matcher.rb +++ b/lib/shoulda/matchers/action_controller/route_matcher.rb @@ -1,36 +1,92 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - - # Ensures that requesting +path+ using +method+ routes to +options+. - # - # If you don't specify a controller, it will use the controller from the - # example group. - # - # +to_param+ is called on the +options+ given. - # - # Examples: - # - # it { should route(:get, '/posts'). - # to(controller: :posts, action: :index) } - # it { should route(:get, '/posts').to('posts#index') } - # it { should route(:get, '/posts/new').to(action: :new) } - # it { should route(:post, '/posts').to(action: :create) } - # it { should route(:get, '/posts/1').to(action: :show, id: 1) } - # it { should route(:get, '/posts/1').to('posts#show', id: 1) } - # it { should route(:get, '/posts/1/edit').to(action: :edit, id: 1) } - # it { should route(:put, '/posts/1').to(action: :update, id: 1) } - # it { should route(:delete, '/posts/1'). - # to(action: :destroy, id: 1) } - # it { should route(:get, '/users/1/posts/1'). - # to(action: :show, id: 1, user_id: 1) } - # it { should route(:get, '/users/1/posts/1'). - # to('posts#show', id: 1, user_id: 1) } + module ActionController + # The `route` matcher tests that a route resolves to a controller, + # action, and params; and that the controller, action, and params + # generates the same route. For an RSpec suite, this is like using a + # combination of `route_to` and `be_routable`. For a Test::Unit suite, it + # provides a more expressive syntax over `assert_routing`. + # + # You can use this matcher either in a controller test case or in a + # routing test case. For instance, given these routes: + # + # My::Application.routes.draw do + # get '/posts', controller: 'posts', action: 'index' + # get '/posts/:id' => 'posts#show' + # end + # + # You could choose to write tests for these routes alongside other tests + # for PostsController: + # + # class PostsController < ApplicationController + # # ... + # end + # + # # RSpec + # describe PostsController do + # it { should route(:get, '/posts').to(action: :index) } + # it { should route(:get, '/posts/1').to(action: :show, id: 1) } + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # should route(:get, '/posts').to(action: 'index') + # should route(:get, '/posts/1').to(action: :show, id: 1) + # end + # + # Or you could place the tests along with other route tests: + # + # # RSpec + # describe 'Routing' do + # it do + # should route(:get, '/posts'). + # to(controller: :posts, action: :index) + # end + # + # it do + # should route(:get, '/posts/1'). + # to('posts#show', id: 1) + # end + # end + # + # # Test::Unit + # class RoutesTest < ActionController::IntegrationTest + # should route(:get, '/posts'). + # to(controller: :posts, action: :index) + # + # should route(:get, '/posts/1'). + # to('posts#show', id: 1) + # end + # + # Notice that in the former case, as we are inside of a test case for + # PostsController, we do not have to specify that the routes resolve to + # this controller. In the latter case we specify this using the + # `controller` key passed to the `to` qualifier. + # + # #### Qualifiers + # + # ##### to + # + # Use `to` to specify the action (along with the controller, if needed) + # that the route resolves to. + # + # # Three ways of saying the same thing (using the example above) + # route(:get, '/posts').to(action: index) + # route(:get, '/posts').to(controller: :posts, action: index) + # route(:get, '/posts').to('posts#index') + # + # If there are parameters in your route, then specify those too: + # + # route(:get, '/posts/1').to('posts#show', id: 1) + # + # @return [RouteMatcher] + # def route(method, path) RouteMatcher.new(method, path, self) end - class RouteMatcher # :nodoc: + # @private + class RouteMatcher def initialize(method, path, context) @method = method @path = path diff --git a/lib/shoulda/matchers/action_controller/route_params.rb b/lib/shoulda/matchers/action_controller/route_params.rb index 3baab7a18..2c379446c 100644 --- a/lib/shoulda/matchers/action_controller/route_params.rb +++ b/lib/shoulda/matchers/action_controller/route_params.rb @@ -1,6 +1,7 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: + module ActionController + # @private class RouteParams def initialize(args) @args = args diff --git a/lib/shoulda/matchers/action_controller/set_session_matcher.rb b/lib/shoulda/matchers/action_controller/set_session_matcher.rb index edb804e4c..52523702a 100644 --- a/lib/shoulda/matchers/action_controller/set_session_matcher.rb +++ b/lib/shoulda/matchers/action_controller/set_session_matcher.rb @@ -1,19 +1,76 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - - # Ensures that a session key was set to the expected value. + module ActionController + # The `set_session` matcher is used to make assertions about the + # `session` hash. + # + # class PostsController < ApplicationController + # def show + # session[:foo] = 'bar' + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } + # + # it { should set_session(:foo) } + # it { should_not set_session(:baz) } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #show' do + # setup { get :show } + # + # should set_session(:foo) + # should_not set_session(:baz) + # end + # end + # + # #### Qualifiers + # + # ##### to + # + # Use `to` to assert that the key in the session hash was set to a + # particular value. + # + # class PostsController < ApplicationController + # def show + # session[:foo] = 'bar' + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } + # + # it { should set_session(:foo).to('bar') } + # it { should_not set_session(:foo).to('something else') } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #show' do + # setup { get :show } + # + # should set_session(:foo).to('bar') + # should_not set_session(:foo).to('something else') + # end + # end # - # Example: + # @return [SetSessionMatcher] # - # it { should set_session(:message) } - # it { should set_session(:user_id).to(@user.id) } - # it { should_not set_session(:user_id) } def set_session(key) SetSessionMatcher.new(key) end - class SetSessionMatcher # :nodoc: + # @private + class SetSessionMatcher def initialize(key) @key = key.to_s end diff --git a/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb b/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb index c43cb1cbe..bbde3aa36 100644 --- a/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +++ b/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb @@ -1,23 +1,156 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActionController # :nodoc: - - # Ensures that the flash contains the given value. Can be a String, a - # Regexp, or nil (indicating that the flash should not be set). + module ActionController + # The `set_the_flash` matcher is used to make assertions about the + # `flash` hash. + # + # class PostsController < ApplicationController + # def index + # flash[:foo] = 'A candy bar' + # end + # + # def destroy + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #index' do + # before { get :index } + # + # it { should set_the_flash } + # end + # + # describe 'DELETE #destroy' do + # before { delete :destroy } + # + # it { should_not set_the_flash } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #index' do + # setup { get :index } + # + # should set_the_flash + # end + # + # context 'DELETE #destroy' do + # setup { delete :destroy } + # + # should_not set_the_flash + # end + # end + # + # #### Qualifiers + # + # ##### [] + # + # Use `[]` to narrow the scope of the matcher to a particular key. + # + # class PostsController < ApplicationController + # def index + # flash[:foo] = 'A candy bar' + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #index' do + # before { get :index } + # + # it { should set_the_flash[:foo] } + # it { should_not set_the_flash[:bar] } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #index' do + # setup { get :show } + # + # should set_the_flash[:foo] + # should_not set_the_flash[:bar] + # end + # end + # + # ##### to + # + # Use `to` to assert that some key was set to a particular value, or that + # some key matches a particular regex. + # + # class PostsController < ApplicationController + # def index + # flash[:foo] = 'A candy bar' + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #index' do + # before { get :index } + # + # it { should set_the_flash.to('A candy bar') } + # it { should set_the_flash.to(/bar/) } + # it { should set_the_flash[:foo].to('bar') } + # it { should_not set_the_flash[:foo].to('something else') } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #index' do + # setup { get :show } + # + # should set_the_flash.to('A candy bar') + # should set_the_flash.to(/bar/) + # should set_the_flash[:foo].to('bar') + # should_not set_the_flash[:foo].to('something else') + # end + # end + # + # ##### now + # + # Use `now` to change the scope of the matcher to use the "now" hash + # instead of the usual "future" hash. + # + # class PostsController < ApplicationController + # def show + # flash.now[:foo] = 'bar' + # end + # end + # + # # RSpec + # describe PostsController do + # describe 'GET #show' do + # before { get :show } + # + # it { should set_the_flash.now } + # it { should set_the_flash[:foo].now } + # it { should set_the_flash[:foo].to('bar').now } + # end + # end + # + # # Test::Unit + # class PostsControllerTest < ActionController::TestCase + # context 'GET #index' do + # setup { get :show } + # + # should set_the_flash.now + # should set_the_flash[:foo].now + # should set_the_flash[:foo].to('bar').now + # end + # end # - # Example: + # @return [SetTheFlashMatcher] # - # it { should set_the_flash } - # it { should set_the_flash.to('Thank you for placing this order.') } - # it { should set_the_flash.to(/created/i) } - # it { should set_the_flash[:alert].to('Password does not match') } - # it { should set_the_flash.to(/logged in/i).now } - # it { should_not set_the_flash } def set_the_flash SetTheFlashMatcher.new end - class SetTheFlashMatcher # :nodoc: + # @private + class SetTheFlashMatcher def initialize @options = {} end diff --git a/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb b/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb index 7fd442a19..c34a1fe4a 100644 --- a/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +++ b/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb @@ -10,10 +10,154 @@ module Shoulda module Matchers module ActionController + # The `permit` matcher tests that an action in your controller receives a + # whitelist of parameters using Rails' Strong Parameters feature + # (specifically that `permit` was called with the correct arguments). + # + # Here's an example: + # + # class UsersController < ApplicationController + # def create + # user = User.create(user_params) + # # ... + # end + # + # private + # + # def user_params + # params.require(:user).permit( + # :first_name, + # :last_name, + # :email, + # :password + # ) + # end + # end + # + # # RSpec + # describe UsersController do + # it do + # should permit(:first_name, :last_name, :email, :password). + # for(:create) + # end + # end + # + # # Test::Unit + # class UsersControllerTest < ActionController::TestCase + # should permit(:first_name, :last_name, :email, :password). + # for(:create) + # end + # + # If your action requires query parameters in order to work, then you'll + # need to supply them: + # + # class UsersController < ApplicationController + # def update + # user = User.find(params[:id]) + # + # if user.update_attributes(user_params) + # # ... + # else + # # ... + # end + # end + # + # private + # + # def user_params + # params.require(:user).permit( + # :first_name, + # :last_name, + # :email, + # :password + # ) + # end + # end + # + # # RSpec + # describe UsersController do + # before do + # create(:user, id: 1) + # end + # + # it do + # should permit(:first_name, :last_name, :email, :password). + # for(:update, params: { id: 1 }) + # end + # end + # + # # Test::Unit + # class UsersControllerTest < ActionController::TestCase + # setup do + # create(:user, id: 1) + # end + # + # should permit(:first_name, :last_name, :email, :password). + # for(:update, params: { id: 1 }) + # end + # + # Finally, if you have an action that isn't one of the seven resourceful + # actions, then you'll need to provide the HTTP verb that it responds to: + # + # Rails.application.routes.draw do + # resources :users do + # member do + # put :toggle + # end + # end + # end + # + # class UsersController < ApplicationController + # def toggle + # user = User.find(params[:id]) + # + # if user.update_attributes(user_params) + # # ... + # else + # # ... + # end + # end + # + # private + # + # def user_params + # params.require(:user).permit(:activated) + # end + # end + # + # # RSpec + # describe UsersController do + # before do + # create(:user, id: 1) + # end + # + # it do + # should permit(:activated).for(:toggle, + # params: { id: 1 }, + # verb: :put + # ) + # end + # end + # + # # Test::Unit + # class UsersControllerTest < ActionController::TestCase + # setup do + # create(:user, id: 1) + # end + # + # should permit(:activated).for(:toggle, + # params: { id: 1 }, + # verb: :put + # ) + # end + # + # @return [StrongParametersMatcher] + # def permit(*params) StrongParametersMatcher.new(params).in_context(self) end + # @private class StrongParametersMatcher attr_writer :stubbed_params @@ -114,12 +258,14 @@ def param_names_as_sentence expected_permitted_params.map(&:inspect).to_sentence end + # @private class ActionNotDefinedError < StandardError def message 'You must specify the controller action using the #for method.' end end + # @private class VerbNotDefinedError < StandardError def message 'You must specify an HTTP verb when using a non-RESTful action. For example: for(:authorize, verb: :post)' diff --git a/lib/shoulda/matchers/active_model.rb b/lib/shoulda/matchers/active_model.rb index d9b0cc409..cbe403faa 100644 --- a/lib/shoulda/matchers/active_model.rb +++ b/lib/shoulda/matchers/active_model.rb @@ -22,33 +22,8 @@ require 'shoulda/matchers/active_model/errors' require 'shoulda/matchers/active_model/have_secure_password_matcher' - module Shoulda module Matchers - # = Matchers for your active record models - # - # These matchers will test most of the validations of ActiveModel::Validations. - # - # describe User do - # it { should validate_presence_of(:name) } - # it { should validate_presence_of(:phone_number) } - # %w(abcd 1234).each do |value| - # it { should_not allow_value(value).for(:phone_number) } - # end - # it { should allow_value('(123) 456-7890').for(:phone_number) } - # it { should_not allow_mass_assignment_of(:password) } - # it { should allow_value('Activated', 'Pending').for(:status).strict } - # it { should_not allow_value('Amazing').for(:status).strict } - # end - # - # These tests work with the following model: - # - # class User < ActiveRecord::Base - # validates_presence_of :name - # validates_presence_of :phone_number - # validates_inclusion_of :status, in: %w(Activated Pending), strict: true - # attr_accessible :name, :phone_number - # end module ActiveModel end end diff --git a/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb b/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb index 7e5f94c7a..24441fc78 100644 --- a/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb @@ -1,21 +1,78 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the attribute can be set on mass update. + module ActiveModel + # The `allow_mass_assignment_of` matcher tests usage of Rails 3's + # `attr_accessible` and `attr_protected` macros, asserting that an + # attribute in your model is contained in either the whitelist or + # blacklist and thus can or cannot be set via mass assignment. + # + # class Post + # include ActiveModel::Model + # include ActiveModel::MassAssignmentSecurity + # attr_accessor :title + # + # attr_accessible :title + # end + # + # class User + # include ActiveModel::Model + # include ActiveModel::MassAssignmentSecurity + # attr_accessor :encrypted_password + # + # attr_protected :encrypted_password + # end + # + # # RSpec + # describe Post do + # it { should allow_mass_assignment_of(:title) } + # end + # + # describe User do + # it { should_not allow_mass_assignment_of(:encrypted_password) } + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should allow_mass_assignment_of(:title) + # end + # + # class UserTest < ActiveSupport::TestCase + # should_not allow_mass_assignment_of(:encrypted_password) + # end + # + # #### Optional qualifiers + # + # ##### as + # + # Use `as` if your mass-assignment rules apply only under a certain role + # *(Rails >= 3.1 only)*. + # + # class Post + # include ActiveModel::Model + # include ActiveModel::MassAssignmentSecurity + # attr_accessor :title + # + # attr_accessible :title, as: :admin + # end # - # it { should_not allow_mass_assignment_of(:password) } - # it { should allow_mass_assignment_of(:first_name) } + # # RSpec + # describe Post do + # it { should allow_mass_assignment_of(:title).as(:admin) } + # end # - # In Rails 3.1 you can check role as well: + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should allow_mass_assignment_of(:title).as(:admin) + # end # - # it { should allow_mass_assignment_of(:first_name).as(:admin) } + # @return [AllowMassAssignmentOfMatcher] # def allow_mass_assignment_of(value) AllowMassAssignmentOfMatcher.new(value) end - class AllowMassAssignmentOfMatcher # :nodoc: + # @private + class AllowMassAssignmentOfMatcher attr_reader :failure_message, :failure_message_when_negated alias failure_message_for_should failure_message diff --git a/lib/shoulda/matchers/active_model/allow_value_matcher.rb b/lib/shoulda/matchers/active_model/allow_value_matcher.rb index 34a1ade70..a71c006cd 100644 --- a/lib/shoulda/matchers/active_model/allow_value_matcher.rb +++ b/lib/shoulda/matchers/active_model/allow_value_matcher.rb @@ -1,23 +1,164 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the attribute can be set to the given value or values. If - # multiple values are given the match succeeds only if all given values - # are allowed. Otherwise, the matcher fails at the first bad value in the - # argument list (the remaining arguments are not processed then). + module ActiveModel + # The `allow_value` matcher is used to test that an attribute of a model + # can or cannot be set to a particular value or values. It is most + # commonly used in conjunction with the `validates_format_of` validation. + # + # #### should + # + # In the positive form, `allow_value` asserts that an attribute can be + # set to one or more values, succeeding if none of the values cause the + # record to be invalid: + # + # class UserProfile + # include ActiveModel::Model + # attr_accessor :website_url + # + # validates_format_of :website_url, with: URI.regexp + # end + # + # # RSpec + # describe UserProfile do + # it do + # should allow_value('http://foo.com', 'http://bar.com/baz'). + # for(:website_url) + # end + # end + # + # # Test::Unit + # class UserProfileTest < ActiveSupport::TestCase + # should allow_value('http://foo.com', 'http://bar.com/baz'). + # for(:website_url) + # end + # + # #### should_not + # + # In the negative form, `allow_value` asserts that an attribute cannot be + # set to one or more values, succeeding if the *first* value causes the + # record to be invalid. + # + # **This can be surprising** so in this case if you need to check that + # *all* of the values are invalid, use separate assertions: + # + # class UserProfile + # include ActiveModel::Model + # attr_accessor :website_url + # + # validates_format_of :website_url, with: URI.regexp + # end + # + # describe UserProfile do + # # One assertion: 'buz' and 'bar' will not be tested + # it { should_not allow_value('fiz', 'buz', 'bar').for(:website_url) } + # + # # Three assertions, all tested separately + # it { should_not allow_value('fiz').for(:website_url) } + # it { should_not allow_value('buz').for(:website_url) } + # it { should_not allow_value('bar').for(:website_url) } + # end + # + # #### Qualifiers + # + # ##### on + # + # Use `on` if your validation applies only under a certain context. + # + # class UserProfile + # include ActiveModel::Model + # attr_accessor :birthday_as_string + # + # validates_format_of :birthday_as_string, + # with: /^(\d+)-(\d+)-(\d+)$/, + # on: :create + # end + # + # # RSpec + # describe UserProfile do + # it do + # should allow_value('2013-01-01'). + # for(:birthday_as_string). + # on(:create) + # end + # end + # + # # Test::Unit + # class UserProfileTest < ActiveSupport::TestCase + # should allow_value('2013-01-01'). + # for(:birthday_as_string). + # on(:create) + # end + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class UserProfile + # include ActiveModel::Model + # attr_accessor :state + # + # validates_format_of :state, + # with: /^(open|closed)$/, + # message: 'State must be open or closed' + # end + # + # # RSpec + # describe UserProfile do + # it do + # should allow_value('open', 'closed'). + # for(:state). + # with_message('State must be open or closed') + # end + # end + # + # # Test::Unit + # class UserProfileTest < ActiveSupport::TestCase + # should allow_value('open', 'closed'). + # for(:state). + # with_message('State must be open or closed') + # end + # + # Use `with_message` with the `:against` option if the attribute the + # validation message is stored under is different from the attribute + # being validated. + # + # class UserProfile + # include ActiveModel::Model + # attr_accessor :sports_team + # + # validate :sports_team_must_be_valid + # + # private + # + # def sports_team_must_be_valid + # if sports_team !~ /^(Broncos|Titans)$/i + # self.errors.add :chosen_sports_team, + # 'Must be either a Broncos fan or a Titans fan' + # end + # end + # end + # + # # RSpec + # describe UserProfile do + # it do + # should allow_value('Broncos', 'Titans'). + # for(:sports_team). + # with_message('Must be either a Broncos or Titans fan', + # against: :chosen_sports_team + # ) + # end + # end # - # Options: - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. If omitted, - # the test looks for any errors in errors.on(:attribute). - # * strict - expects the model to raise an exception when the - # validation fails rather than adding to the errors collection. Used for - # testing `validates!` and the `strict: true` validation options. + # # Test::Unit + # class UserProfileTest < ActiveSupport::TestCase + # should allow_value('Broncos', 'Titans'). + # for(:sports_team). + # with_message('Must be either a Broncos or Titans fan', + # against: :chosen_sports_team + # ) + # end # - # Example: - # it { should_not allow_value('bad').for(:isbn) } - # it { should allow_value('isbn 1 2345 6789 0').for(:isbn) } + # @return [AllowValueMatcher] # def allow_value(*values) if values.empty? @@ -27,7 +168,8 @@ def allow_value(*values) end end - class AllowValueMatcher # :nodoc: + # @private + class AllowValueMatcher include Helpers attr_accessor :attribute_with_message diff --git a/lib/shoulda/matchers/active_model/disallow_value_matcher.rb b/lib/shoulda/matchers/active_model/disallow_value_matcher.rb index b8bcc407d..066e1d644 100644 --- a/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +++ b/lib/shoulda/matchers/active_model/disallow_value_matcher.rb @@ -1,13 +1,13 @@ require 'forwardable' -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - class DisallowValueMatcher # :nodoc: + module ActiveModel + # @private + class DisallowValueMatcher extend Forwardable def_delegators :allow_matcher, :_after_setting_value - def initialize(value) @allow_matcher = AllowValueMatcher.new(value) end diff --git a/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb b/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb index 6e68f8087..8ea3b7661 100644 --- a/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb @@ -1,24 +1,96 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensure that the attribute's value is not in the range specified + module ActiveModel + # The `ensure_exclusion_of` matcher tests usage of the + # `validates_exclusion_of` validation, asserting that an attribute cannot + # take a blacklist of values, and inversely, can take values outside of + # this list. + # + # If your blacklist is an array of values, use `in_array`: + # + # class Game + # include ActiveModel::Model + # attr_accessor :supported_os + # + # validates_exclusion_of :supported_os, in: ['Mac', 'Linux'] + # end + # + # # RSpec + # describe Game do + # it do + # should ensure_exclusion_of(:supported_os). + # in_array(['Mac', 'Linux']) + # end + # end + # + # # Test::Unit + # class GameTest < ActiveSupport::TestCase + # should ensure_exclusion_of(:supported_os). + # in_array(['Mac', 'Linux']) + # end + # + # If your blacklist is a range of values, use `in_rnage`: + # + # class Game + # include ActiveModel::Model + # attr_accessor :supported_os + # + # validates_exclusion_of :supported_os, in: ['Mac', 'Linux'] + # end + # + # # RSpec + # describe Game do + # it do + # should ensure_exclusion_of(:floors_with_enemies). + # in_range(5..8) + # end + # end + # + # # Test::Unit + # class GameTest < ActiveSupport::TestCase + # should ensure_exclusion_of(:floors_with_enemies). + # in_range(5..8) + # end + # + # #### Qualifiers + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class Game + # include ActiveModel::Model + # attr_accessor :weapon + # + # validates_exclusion_of :weapon, + # in: ['pistol', 'paintball gun', 'stick'], + # message: 'You chose a puny weapon' + # end + # + # # RSpec + # describe Game do + # it do + # should ensure_exclusion_of(:weapon). + # in_array(['pistol', 'paintball gun', 'stick']). + # with_message('You chose a puny weapon') + # end + # end # - # Options: - # * in_array - the array of not allowed values for this attribute - # * in_range - the range of not allowed values for this attribute - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :exclusion. + # # Test::Unit + # class GameTest < ActiveSupport::TestCase + # should ensure_exclusion_of(:weapon). + # in_array(['pistol', 'paintball gun', 'stick']). + # with_message('You chose a puny weapon') + # end # - # Example: - # it { should ensure_exclusion_of(:age).in_range(30..60) } + # @return [EnsureExclusionOfMatcher] # def ensure_exclusion_of(attr) EnsureExclusionOfMatcher.new(attr) end - class EnsureExclusionOfMatcher < ValidationMatcher # :nodoc: + # @private + class EnsureExclusionOfMatcher < ValidationMatcher def in_array(array) @array = array self diff --git a/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb b/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb index d8947200e..cf672ac58 100644 --- a/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb @@ -1,29 +1,225 @@ require 'bigdecimal' -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensure that the attribute's value is in the range specified + module ActiveModel + # The `ensure_inclusion_of` matcher tests usage of the + # `validates_inclusion_of` validation, asserting that an attribute can + # take a whitelist of values and cannot take values outside of this list. + # + # If your whitelist is an array of values, use `in_array`: + # + # class Issue + # include ActiveModel::Model + # attr_accessor :state + # + # validates_inclusion_of :state, in: %w(open resolved unresolved) + # end + # + # # RSpec + # describe Issue do + # it do + # should ensure_inclusion_of(:state). + # in_array(%w(open resolved unresolved)) + # end + # end + # + # # Test::Unit + # class IssueTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:state). + # in_array(%w(open resolved unresolved)) + # end + # + # If your whitelist is a range of values, use `in_range`: + # + # class Issue + # include ActiveModel::Model + # attr_accessor :priority + # + # validates_inclusion_of :priority, in: 1..5 + # end + # + # # RSpec + # describe Issue do + # it { should ensure_inclusion_of(:state).in_range(1..5) } + # end + # + # # Test::Unit + # class IssueTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:state).in_range(1..5) + # end + # + # #### Optional qualifiers + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class Issue + # include ActiveModel::Model + # attr_accessor :severity + # + # validates_inclusion_of :severity, + # in: %w(low medium high), + # message: 'Severity must be low, medium, or high' + # end + # + # # RSpec + # describe Issue do + # it do + # should ensure_inclusion_of(:severity). + # in_array(%w(low medium high)). + # with_message('Severity must be low, medium, or high') + # end + # end + # + # # Test::Unit + # class IssueTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:severity). + # in_array(%w(low medium high)). + # with_message('Severity must be low, medium, or high') + # end + # + # ##### with_low_message + # + # Use `with_low_message` if you have a custom validation message for when + # a given value is too low. + # + # class Person + # include ActiveModel::Model + # attr_accessor :age + # + # validate :age_must_be_valid + # + # private + # + # def age_must_be_valid + # if age < 65 + # self.errors.add :age, 'You do not receive any benefits' + # end + # end + # end + # + # # RSpec + # describe Person do + # it do + # should ensure_inclusion_of(:age). + # in_range(0..65). + # with_low_message('You do not receive any benefits') + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:age). + # in_range(0..65). + # with_low_message('You do not receive any benefits') + # end + # + # ##### with_high_message + # + # Use `with_high_message` if you have a custom validation message for + # when a given value is too high. + # + # class Person + # include ActiveModel::Model + # attr_accessor :age + # + # validate :age_must_be_valid + # + # private + # + # def age_must_be_valid + # if age > 21 + # self.errors.add :age, "You're too old for this stuff" + # end + # end + # end + # + # # RSpec + # describe Person do + # it do + # should ensure_inclusion_of(:age). + # in_range(0..21). + # with_high_message("You're too old for this stuff") + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:age). + # in_range(0..21). + # with_high_message("You're too old for this stuff") + # end + # + # ##### allow_nil + # + # Use `allow_nil` to assert that the attribute allows nil. + # + # class Issue + # include ActiveModel::Model + # attr_accessor :state + # + # validates_presence_of :state + # validates_inclusion_of :state, + # in: %w(open resolved unresolved), + # allow_nil: true + # end + # + # # RSpec + # describe Issue do + # it do + # should ensure_inclusion_of(:state). + # in_array(%w(open resolved unresolved)). + # allow_nil + # end + # end + # + # # Test::Unit + # class IssueTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:state). + # in_array(%w(open resolved unresolved)). + # allow_nil + # end + # + # ##### allow_blank + # + # Use `allow_blank` to assert that the attribute allows blank. + # + # class Issue + # include ActiveModel::Model + # attr_accessor :state + # + # validates_presence_of :state + # validates_inclusion_of :state, + # in: %w(open resolved unresolved), + # allow_blank: true + # end + # + # # RSpec + # describe Issue do + # it do + # should ensure_inclusion_of(:state). + # in_array(%w(open resolved unresolved)). + # allow_blank + # end + # end # - # Options: - # * in_array - the array of allowed values for this attribute - # * in_range - the range of allowed values for this attribute - # * with_low_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :inclusion. - # * with_high_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :inclusion. + # # Test::Unit + # class IssueTest < ActiveSupport::TestCase + # should ensure_inclusion_of(:state). + # in_array(%w(open resolved unresolved)). + # allow_blank + # end # - # Example: - # it { should ensure_inclusion_of(:age).in_range(0..100) } + # @return [EnsureInclusionOfMatcher] # def ensure_inclusion_of(attr) EnsureInclusionOfMatcher.new(attr) end - class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc: + # @private + class EnsureInclusionOfMatcher < ValidationMatcher ARBITRARY_OUTSIDE_STRING = 'shouldamatchersteststring' ARBITRARY_OUTSIDE_FIXNUM = 123456789 ARBITRARY_OUTSIDE_DECIMAL = BigDecimal.new('0.123456789') diff --git a/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb b/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb index 1c3223ed5..e946fe435 100644 --- a/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb @@ -1,40 +1,204 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the length of the attribute is validated. Only works with - # string/text columns because it uses a string to check length. - # - # Options: - # * is_at_least - minimum length of this attribute - # * is_at_most - maximum length of this attribute - # * is_equal_to - exact requred length of this attribute - # * with_short_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :too_short. - # * with_long_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :too_long. - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :wrong_length. Used in conjunction with - # is_equal_to. - # - # Examples: - # it { should ensure_length_of(:password). - # is_at_least(6). - # is_at_most(20) } - # it { should ensure_length_of(:name). - # is_at_least(3). - # with_short_message(/not long enough/) } - # it { should ensure_length_of(:ssn). - # is_equal_to(9). - # with_message(/is invalid/) } + module ActiveModel + # The `ensure_length_of` matcher tests usage of the `validates_length_of` + # matcher. Note that this matcher is intended to be used against string + # columns and not integer columns. + # + # #### Qualifiers + # + # ##### is_at_least + # + # Use `is_at_least` to test usage of the `:minimum` option. This asserts + # that the attribute can take a string which is equal to or longer than + # the given length and cannot take a string which is shorter. + # + # class User + # include ActiveModel::Model + # attr_accessor :bio + # + # validates_length_of :bio, minimum: 15 + # end + # + # # RSpec + # + # describe User do + # it { should ensure_length_of(:bio).is_at_least(15) } + # end + # + # # Test::Unit + # + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:bio).is_at_least(15) + # end + # + # ##### is_at_most + # + # Use `is_at_most` to test usage of the `:maximum` option. This asserts + # that the attribute can take a string which is equal to or shorter than + # the given length and cannot take a string which is longer. + # + # class User + # include ActiveModel::Model + # attr_accessor :status_update + # + # validates_length_of :status_update, maximum: 140 + # end + # + # # RSpec + # describe User do + # it { should ensure_length_of(:status_update).is_at_most(140) } + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:status_update).is_at_most(140) + # end + # + # ##### is_equal_to + # + # Use `is_at_equal` to test usage of the `:is` option. This asserts that + # the attribute can take a string which is exactly equal to the given + # length and cannot take a string which is shorter or longer. + # + # class User + # include ActiveModel::Model + # attr_accessor :favorite_superhero + # + # validates_length_of :favorite_superhero, is: 6 + # end + # + # # RSpec + # describe User do + # it { should ensure_length_of(:favorite_superhero).is_equal_to(6) } + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:favorite_superhero).is_equal_to(6) + # end + # + # ##### is_at_least + is_at_most + # + # Use `is_at_least` and `is_at_most` together to test usage of the `:in` + # option. + # + # class User + # include ActiveModel::Model + # attr_accessor :password + # + # validates_length_of :password, in: 5..30 + # end + # + # # RSpec + # describe User do + # it do + # should ensure_length_of(:password). + # is_at_least(5).is_at_most(30) + # end + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:password). + # is_at_least(5).is_at_most(30) + # end + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class User + # include ActiveModel::Model + # attr_accessor :api_token + # + # validates_length_of :api_token, + # minimum: 10, + # message: "Password isn't long enough" + # end + # + # # RSpec + # describe User do + # it do + # should ensure_length_of(:password). + # is_at_least(10). + # with_message("Password isn't long enough") + # end + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:password). + # is_at_least(10). + # with_message("Password isn't long enough") + # end + # + # ##### with_short_message + # + # Use `with_short_message` if you are using a custom "too short" message. + # + # class User + # include ActiveModel::Model + # attr_accessor :secret_key + # + # validates_length_of :secret_key, + # in: 15..100, + # too_short: 'Secret key must be more than 15 characters' + # end + # + # # RSpec + # describe User do + # it do + # should ensure_length_of(:secret_key). + # is_at_least(15). + # with_short_message('Secret key must be more than 15 characters') + # end + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:secret_key). + # is_at_least(15). + # with_short_message('Secret key must be more than 15 characters') + # end + # + # ##### with_long_message + # + # Use `with_long_message` if you are using a custom "too long" message. + # + # class User + # include ActiveModel::Model + # attr_accessor :secret_key + # + # validates_length_of :secret_key, + # in: 15..100, + # too_long: 'Secret key must be less than 100 characters' + # end + # + # # RSpec + # describe User do + # it do + # should ensure_length_of(:secret_key). + # is_at_most(100). + # with_long_message('Secret key must be less than 100 characters') + # end + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should ensure_length_of(:secret_key). + # is_at_most(100). + # with_long_message('Secret key must be less than 100 characters') + # end + # + # @return [EnsureLengthOfMatcher] + # def ensure_length_of(attr) EnsureLengthOfMatcher.new(attr) end - class EnsureLengthOfMatcher < ValidationMatcher # :nodoc: + # @private + class EnsureLengthOfMatcher < ValidationMatcher include Helpers def initialize(attribute) diff --git a/lib/shoulda/matchers/active_model/errors.rb b/lib/shoulda/matchers/active_model/errors.rb index 7aedc6106..d84804304 100644 --- a/lib/shoulda/matchers/active_model/errors.rb +++ b/lib/shoulda/matchers/active_model/errors.rb @@ -1,8 +1,10 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel + # @private class CouldNotDetermineValueOutsideOfArray < RuntimeError; end + # @private class NonNullableBooleanError < Shoulda::Matchers::Error def self.create(attribute) super(attribute: attribute) @@ -19,6 +21,7 @@ def message end end + # @private class CouldNotSetPasswordError < Shoulda::Matchers::Error def self.create(model) super(model: model) diff --git a/lib/shoulda/matchers/active_model/exception_message_finder.rb b/lib/shoulda/matchers/active_model/exception_message_finder.rb index 9d12ab538..886863f78 100644 --- a/lib/shoulda/matchers/active_model/exception_message_finder.rb +++ b/lib/shoulda/matchers/active_model/exception_message_finder.rb @@ -1,7 +1,7 @@ module Shoulda module Matchers module ActiveModel - # Finds message information from exceptions thrown by #valid? + # @private class ExceptionMessageFinder def initialize(instance, attribute, context=nil) @instance = instance diff --git a/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb b/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb index d4455a479..b53d7fec2 100644 --- a/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +++ b/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb @@ -1,16 +1,37 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the model exhibits behavior added by has_secure_password. + module ActiveModel + # The `have_secure_password` matcher tests usage of the + # `has_secure_password` macro. + # + # #### Example + # + # class User + # include ActiveModel::Model + # include ActiveModel::SecurePassword + # attr_accessor :password + # + # has_secure_password + # end + # + # # RSpec + # describe User do + # it { should have_secure_password } + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should have_secure_password + # end + # + # @return [HaveSecurePasswordMatcher] # - # Example: - # it { should have_secure_password } def have_secure_password HaveSecurePasswordMatcher.new end - class HaveSecurePasswordMatcher # :nodoc: + # @private + class HaveSecurePasswordMatcher attr_reader :failure_message alias failure_message_for_should failure_message diff --git a/lib/shoulda/matchers/active_model/helpers.rb b/lib/shoulda/matchers/active_model/helpers.rb index b3e3bb7aa..f190ff6bd 100644 --- a/lib/shoulda/matchers/active_model/helpers.rb +++ b/lib/shoulda/matchers/active_model/helpers.rb @@ -1,8 +1,9 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel + # @private module Helpers - def pretty_error_messages(obj) # :nodoc: + def pretty_error_messages(obj) obj.errors.map do |attribute, model| msg = "#{attribute} #{model}" if attribute.to_sym != :base && obj.respond_to?(attribute) diff --git a/lib/shoulda/matchers/active_model/numericality_matchers.rb b/lib/shoulda/matchers/active_model/numericality_matchers.rb new file mode 100644 index 000000000..fe649c66c --- /dev/null +++ b/lib/shoulda/matchers/active_model/numericality_matchers.rb @@ -0,0 +1,9 @@ +module Shoulda + module Matchers + module ActiveModel + # @private + module NumericalityMatchers + end + end + end +end diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb index c792d281b..1e799c7be 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb @@ -1,11 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel module NumericalityMatchers - # Examples: - # it { should validate_numericality_of(:attr). - # is_greater_than(6). - # less_than(20)...(and so on) } + # @private class ComparisonMatcher < ValidationMatcher ERROR_MESSAGES = { :> => :greater_than, diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb index 5b3138ee9..d744ce18a 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb @@ -1,8 +1,9 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel module NumericalityMatchers - class EvenNumberMatcher < NumericTypeMatcher # :nodoc: + # @private + class EvenNumberMatcher < NumericTypeMatcher NON_EVEN_NUMBER_VALUE = 1 def initialize(attribute, options = {}) diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb index 58085f266..7e8cda6fe 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel module NumericalityMatchers + # @private class NumericTypeMatcher def initialize raise NotImplementedError diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb index fb6e57830..c1538079b 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb @@ -1,8 +1,9 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel module NumericalityMatchers - class OddNumberMatcher < NumericTypeMatcher # :nodoc: + # @private + class OddNumberMatcher < NumericTypeMatcher NON_ODD_NUMBER_VALUE = 2 def initialize(attribute, options = {}) diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb index 71c4dd2f5..d89872f7f 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb @@ -1,8 +1,9 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: + module ActiveModel module NumericalityMatchers - class OnlyIntegerMatcher < NumericTypeMatcher # :nodoc: + # @private + class OnlyIntegerMatcher NON_INTEGER_VALUE = 0.1 def initialize(attribute) @attribute = attribute diff --git a/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb index 23b09c2cc..0bdf364e7 100644 --- a/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb @@ -1,24 +1,62 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the model is not valid if the given attribute is present. + module ActiveModel + # The `validate_absence_of` matcher tests the usage of the + # `validates_absence_of` validation. + # + # class Artillery + # include ActiveModel::Model + # attr_accessor :arms + # + # validates_absence_of :arms + # end + # + # # RSpec + # describe Artillery do + # it { should validate_absence_of(:arms) } + # end + # + # # Test::Unit + # class ArtilleryTest < ActiveSupport::TestCase + # should validate_absence_of(:arms) + # end + # + # #### Qualifiers + # + # ##### with_message # - # Options: - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or String. - # Defaults to the translation for :present. + # Use `with_message` if you are using a custom validation message. + # + # class Artillery + # include ActiveModel::Model + # attr_accessor :arms + # + # validates_absence_of :arms, + # message: "We're fresh outta arms here, soldier!" + # end + # + # # RSpec + # describe Artillery do + # it do + # should validate_absence_of(:arms). + # with_message("We're fresh outta arms here, soldier!") + # end + # end + # + # # Test::Unit + # class ArtilleryTest < ActiveSupport::TestCase + # should validate_absence_of(:arms). + # with_message("We're fresh outta arms here, soldier!") + # end + # + # @return [ValidateAbsenceOfMatcher} # - # 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: - + # @private + class ValidateAbsenceOfMatcher < ValidationMatcher def with_message(message) @expected_message = message self diff --git a/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb index 336834b4b..61d33c7ba 100644 --- a/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb @@ -1,24 +1,62 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the model cannot be saved if the given attribute is not - # accepted. + module ActiveModel + # The `validate_acceptance_of` matcher tests usage of the + # `validates_acceptance_of` validation. + # + # class Registration + # include ActiveModel::Model + # attr_accessor :eula + # + # validates_acceptance_of :eula + # end + # + # # RSpec + # describe Registration do + # it { should validate_acceptance_of(:eula) } + # end + # + # # Test::Unit + # class RegistrationTest < ActiveSupport::TestCase + # should validate_acceptance_of(:eula) + # end + # + # #### Qualifiers # - # Options: - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :accepted. + # ##### with_message # - # Example: - # it { should validate_acceptance_of(:eula) } + # Use `with_message` if you are using a custom validation message. + # + # class Registration + # include ActiveModel::Model + # attr_accessor :terms_of_service + # + # validates_acceptance_of :terms_of_service, + # message: 'You must accept the terms of service' + # end + # + # # RSpec + # describe Registration do + # it do + # should validate_acceptance_of(:terms_of_service). + # with_message('You must accept the terms of service') + # end + # end + # + # # Test::Unit + # class RegistrationTest < ActiveSupport::TestCase + # should validate_acceptance_of(:terms_of_service). + # with_message('You must accept the terms of service') + # end + # + # @return [ValidateAcceptanceOfMatcher] # def validate_acceptance_of(attr) ValidateAcceptanceOfMatcher.new(attr) end - class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc: - + # @private + class ValidateAcceptanceOfMatcher < ValidationMatcher def with_message(message) if message @expected_message = message diff --git a/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb index 45b68bdd7..5aa6bfd0d 100644 --- a/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb @@ -1,16 +1,62 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - # Ensures that the model's attribute matches confirmation + module ActiveModel + # The `validate_confirmation_of` matcher tests usage of the + # `validates_confirmation_of` validation. # - # Example: - # it { should validate_confirmation_of(:password) } + # class User + # include ActiveModel::Model + # attr_accessor :email + # + # validates_confirmation_of :email + # end + # + # # RSpec + # describe User do + # it { should validate_confirmation_of(:email) } + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should validate_confirmation_of(:email) + # end + # + # #### Qualifiers + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class User + # include ActiveModel::Model + # attr_accessor :password + # + # validates_confirmation_of :password, + # message: 'Please re-enter your password' + # end + # + # # RSpec + # describe User do + # it do + # should validate_confirmation_of(:password). + # with_message('Please re-enter your password') + # end + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should validate_confirmation_of(:password). + # with_message('Please re-enter your password') + # end + # + # @return [ValidateConfirmationOfMatcher] # def validate_confirmation_of(attr) ValidateConfirmationOfMatcher.new(attr) end - class ValidateConfirmationOfMatcher < ValidationMatcher # :nodoc: + # @private + class ValidateConfirmationOfMatcher < ValidationMatcher include Helpers attr_reader :attribute, :confirmation_attribute diff --git a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb index 13624ed88..7848d6dba 100644 --- a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb @@ -1,28 +1,284 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - # Ensure that the attribute is numeric. - # - # Options: - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or string. Defaults to the - # translation for :not_a_number. - # * only_integer - allows only integer values - # * odd - Specifies the value must be an odd number. - # * even - Specifies the value must be an even number. - # * allow_nil - allows nil values - # - # Examples: - # it { should validate_numericality_of(:price) } - # it { should validate_numericality_of(:age).only_integer } - # it { should validate_numericality_of(:frequency).odd } - # it { should validate_numericality_of(:frequency).even } - # it { should validate_numericality_of(:rank).is_less_than_or_equal_to(10).allow_nil } + module ActiveModel + # The `validate_numericality_of` matcher tests usage of the + # `validates_numericality_of` validation. + # + # class Person + # include ActiveModel::Model + # attr_accessor :gpa + # + # validates_numericality_of :gpa + # end + # + # # RSpec + # describe Person do + # it { should validate_numericality_of(:gpa) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:gpa) + # end + # + # #### Qualifiers + # + # ##### only_integer + # + # Use `only_integer` to test usage of the `:only_integer` option. This + # asserts that your attribute only allows integer numbers and disallows + # non-integer ones. + # + # class Person + # include ActiveModel::Model + # attr_accessor :age + # + # validates_numericality_of :age, only_integer: true + # end + # + # # RSpec + # describe Person do + # it { should validate_numericality_of(:age).only_integer } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:age).only_integer + # end + # + # ##### is_less_than + # + # Use `is_less_than` to test usage of the the `:less_than` option. This + # asserts that the attribute can take a number which is less than the + # given value and cannot take a number which is greater than or equal to + # it. + # + # class Person + # include ActiveModel::Model + # attr_accessor :number_of_cars + # + # validates_numericality_of :number_of_cars, less_than: 2 + # end + # + # # RSpec + # describe Person do + # it do + # should validate_numericality_of(:number_of_cars). + # is_less_than(2) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:number_of_cars). + # is_less_than(2) + # end + # + # ##### is_less_than_or_equal_to + # + # Use `is_less_than_or_equal_to` to test usage of the + # `:less_than_or_equal_to` option. This asserts that the attribute can + # take a number which is less than or equal to the given value and cannot + # take a number which is greater than it. + # + # class Person + # include ActiveModel::Model + # attr_accessor :birth_year + # + # validates_numericality_of :birth_year, less_than_or_equal_to: 1987 + # end + # + # # RSpec + # describe Person do + # it do + # should validate_numericality_of(:birth_year). + # is_less_than_or_equal_to(1987) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:birth_year). + # is_less_than_or_equal_to(1987) + # end + # + # ##### is_equal_to + # + # Use `is_equal_to` to test usage of the `:equal_to` option. This asserts + # that the attribute can take a number which is equal to the given value + # and cannot take a number which is not equal. + # + # class Person + # include ActiveModel::Model + # attr_accessor :weight + # + # validates_numericality_of :weight, equal_to: 150 + # end + # + # # RSpec + # describe Person do + # it { should validate_numericality_of(:weight).is_equal_to(150) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:weight).is_equal_to(150) + # end + # + # ##### is_greater_than_or_equal_to + # + # Use `is_greater_than_or_equal_to` to test usage of the + # `:greater_than_or_equal_to` option. This asserts that the attribute can + # take a number which is greater than or equal to the given value and + # cannot take a number which is less than it. + # + # class Person + # include ActiveModel::Model + # attr_accessor :height + # + # validates_numericality_of :height, greater_than_or_equal_to: 55 + # end + # + # # RSpec + # describe Person do + # it do + # should validate_numericality_of(:height). + # is_greater_than_or_equal_to(55) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:height). + # is_greater_than_or_equal_to(55) + # end + # + # ##### is_greater_than + # + # Use `is_greater_than` to test usage of tthe `:greater_than` option. + # This asserts that the attribute can take a number which is greater than + # the given value and cannot take a number less than or equal to it. + # + # class Person + # include ActiveModel::Model + # attr_accessor :legal_age + # + # validates_numericality_of :legal_age, greater_than: 21 + # end + # + # # RSpec + # describe Person do + # it do + # should validate_numericality_of(:legal_age). + # is_greater_than(21) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:legal_age). + # is_greater_than(21) + # end + # + # ##### even + # + # Use `even` to test usage of the `:even` option. This asserts that the + # attribute can take odd numbers and cannot take even ones. + # + # class Person + # include ActiveModel::Model + # attr_accessor :birth_month + # + # validates_numericality_of :birth_month, even: true + # end + # + # # RSpec + # describe Person do + # it { should validate_numericality_of(:birth_month).even } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:birth_month).even + # end + # + # ##### odd + # + # Use `odd` to test usage of the `:odd` option. This asserts that the + # attribute can take a number which is odd and cannot take a number which + # is even. + # + # class Person + # include ActiveModel::Model + # attr_accessor :birth_day + # + # validates_numericality_of :birth_day, odd: true + # end + # + # # RSpec + # describe Person do + # it { should validate_numericality_of(:birth_day).odd } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:birth_day).odd + # end + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class Person + # include ActiveModel::Model + # attr_accessor :number_of_dependents + # + # validates_numericality_of :number_of_dependents, + # message: 'Number of dependents must be a number' + # end + # + # # RSpec + # describe Person do + # it do + # should validate_numericality_of(:number_of_dependents). + # with_message('Number of dependents must be a number') + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:number_of_dependents). + # with_message('Number of dependents must be a number') + # end + # + # ##### allow_nil + # + # Use `allow_nil` to assert that the attribute allows nil. + # + # class Age + # include ActiveModel::Model + # attr_accessor :age + # + # validates_numericality_of :age, allow_nil: true + # end + # + # # RSpec + # describe Post do + # it { should validate_numericality_of(:age).allow_nil } + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should validate_numericality_of(:age).allow_nil + # end + # + # @return [ValidateNumericalityOfMatcher] # def validate_numericality_of(attr) ValidateNumericalityOfMatcher.new(attr) end + # @private class ValidateNumericalityOfMatcher NUMERIC_NAME = 'numbers' NON_NUMERIC_VALUE = 'abcd' diff --git a/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb index 200e13925..aa0dc3734 100644 --- a/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb @@ -1,25 +1,95 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - - # Ensures that the model is not valid if the given attribute is not - # present. + module ActiveModel + # The `validate_presence_of` matcher tests usage of the + # `validates_presence_of` validation. + # + # class Robot + # include ActiveModel::Model + # attr_accessor :arms + # + # validates_presence_of :arms + # end + # + # # RSpec + # describe Robot do + # it { should validate_presence_of(:arms) } + # end + # + # # Test::Unit + # class RobotTest < ActiveSupport::TestCase + # should validate_presence_of(:arms) + # end + # + # #### Caveats + # + # Under Rails 4 and greater, if your model `has_secure_password` and you + # are validating presence of the password using a record whose password + # has already been set prior to calling the matcher, you will be + # instructed to use a record whose password is empty instead. + # + # For example, given this scenario: + # + # class User < ActiveRecord::Base + # has_secure_password validations: false + # + # validates_presence_of :password + # end + # + # describe User do + # subject { User.new(password: '123456') } + # + # it { should validate_presence_of(:password) } + # end + # + # the above test will raise an error like this: + # + # The validation failed because your User model declares + # `has_secure_password`, and `validate_presence_of` was called on a + # user which has `password` already set to a value. Please use a user + # with an empty `password` instead. + # + # This happens because `has_secure_password` itself overrides your model + # so that it is impossible to set `password` to nil. This means that it is + # impossible to test that setting `password` to nil places your model in + # an invalid state (which in turn means that the validation itself is + # unnecessary). + # + # #### Qualifiers + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class Robot + # include ActiveModel::Model + # attr_accessor :legs + # + # validates_presence_of :legs, message: 'Robot has no legs' + # end + # + # # RSpec + # describe Robot do + # it do + # should validate_presence_of(:legs). + # with_message('Robot has no legs') + # end + # end # - # Options: - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or String. - # Defaults to the translation for :blank. + # # Test::Unit + # class RobotTest < ActiveSupport::TestCase + # should validate_presence_of(:legs). + # with_message('Robot has no legs') + # end # - # Examples: - # it { should validate_presence_of(:name) } - # it { should validate_presence_of(:name). - # with_message(/is not optional/) } + # @return [ValidatePresenceOfMatcher] # def validate_presence_of(attr) ValidatePresenceOfMatcher.new(attr) end - class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc: + # @private + class ValidatePresenceOfMatcher < ValidationMatcher def with_message(message) @expected_message = message if message self diff --git a/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb index 377000c82..901706a63 100644 --- a/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb @@ -1,38 +1,178 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - # Ensures that the model is invalid if the given attribute is not unique. - # It uses the first existing record or creates a new one if no record - # exists in the database. It simply uses `validate: false` to get - # around validations, so it will probably fail if there are `NOT NULL` - # constraints. In that case, you must create a record before calling - # `validate_uniqueness_of`. - # - # Example: - # it { should validate_uniqueness_of(:email) } - # - # Options: - # - # * with_message - value the test expects to find in - # errors.on(:attribute). Regexp or String. - # Defaults to the translation for :taken. - # * scoped_to - field(s) to scope the uniqueness to. - # * case_insensitive - ensures that the validation does not - # check case. Off by default. Ignored by non-text attributes. - # - # Examples: - # it { should validate_uniqueness_of(:keyword) } - # it { should validate_uniqueness_of(:keyword).with_message(/dup/) } - # it { should validate_uniqueness_of(:email).scoped_to(:name) } - # it { should validate_uniqueness_of(:email). - # scoped_to(:first_name, :last_name) } - # it { should validate_uniqueness_of(:keyword).case_insensitive } + module ActiveModel + # The `validate_uniqueness_of` matcher tests usage of the + # `validates_uniqueness_of` validation. It first checks for an existing + # instance of your model in the database, creating one if necessary. It + # then takes a new record and asserts that it fails validation if the + # attribute or attributes you've specified in the validation are set to + # values which are the same as those of the pre-existing record (thereby + # failing the uniqueness check). + # + # class Post < ActiveRecord::Base + # validates_uniqueness_of :permalink + # end + # + # # RSpec + # describe Post do + # it { should validate_uniqueness_of(:permalink) } + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should validate_uniqueness_of(:permalink) + # end + # + # #### Caveat + # + # This matcher works a bit differently than other matchers. As noted + # before, it will create an instance of your model if one doesn't already + # exist. Sometimes this step fails, especially if you have database-level + # restrictions on any attributes other than the one which is unique. In + # this case, the solution is to **create a record manually** before you + # call `validate_uniqueness_of`. + # + # For example, say you have the following migration and model: + # + # class CreatePosts < ActiveRecord::Migration + # def change + # create_table :posts do |t| + # t.string :title + # t.text :content, null: false + # end + # end + # end + # + # class Post < ActiveRecord::Base + # validates :title, uniqueness: true + # end + # + # You may be tempted to test the model like this: + # + # describe Post do + # it { should validate_uniqueness_of(:title) } + # end + # + # However, running this test will fail with something like: + # + # Failures: + # + # 1) Post should require case sensitive unique value for title + # Failure/Error: it { should validate_uniqueness_of(:title) } + # ActiveRecord::StatementInvalid: + # SQLite3::ConstraintException: posts.content may not be NULL: INSERT INTO "posts" ("title") VALUES (?) + # + # To fix this, you'll need to write this instead: + # + # describe Post do + # it do + # Post.create!(content: 'Here is the content') + # should validate_uniqueness_of(:title) + # end + # end + # + # Or, if you're using + # [FactoryGirl](http://github.com/thoughtbot/factory_girl) and you have a + # `post` factory defined which automatically sets `content`, you can say: + # + # describe Post do + # it do + # FactoryGirl.create(:post) + # should validate_uniqueness_of(:title) + # end + # end + # + # #### Qualifiers + # + # ##### with_message + # + # Use `with_message` if you are using a custom validation message. + # + # class Post < ActiveRecord::Base + # validates_uniqueness_of :title, message: 'Please choose another title' + # end + # + # # RSpec + # describe Post do + # it do + # should validate_uniqueness_of(:title). + # with_message('Please choose another title') + # end + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should validate_uniqueness_of(:title). + # with_message('Please choose another title') + # end + # + # ##### scoped_to + # + # Use `scoped_to` to test usage of the `:scope` option. This asserts that + # a new record fails validation if not only the primary attribute is not + # unique, but the scoped attributes are not unique either. + # + # class Post < ActiveRecord::Base + # validates_uniqueness_of :slug, scope: :user_id + # end + # + # # RSpec + # describe Post do + # it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) } + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should validate_uniqueness_of(:slug).scoped_to(:journal_id) + # end + # + # ##### case_insensitive + # + # Use `case_insensitive` to test usage of the `:case_sensitive` option + # with a false value. This asserts that the uniquable attributes fail + # validation even if their values are a different case than corresponding + # attributes in the pre-existing record. + # + # class Post < ActiveRecord::Base + # validates_uniqueness_of :key, case_sensitive: false + # end + # + # # RSpec + # describe Post do + # it { should validate_uniqueness_of(:key).case_insensitive } + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should validate_uniqueness_of(:key).case_insensitive + # end + # + # ##### allow_nil + # + # Use `allow_nil` to assert that the attribute allows nil. + # + # class Post < ActiveRecord::Base + # validates_uniqueness_of :author_id, allow_nil: true + # end + # + # # RSpec + # describe Post do + # it { should validate_uniqueness_of(:author_id).allow_nil } + # end + # + # # Test::Unit + # class PostTest < ActiveSupport::TestCase + # should validate_uniqueness_of(:author_id).allow_nil + # end + # + # @return [ValidateUniquenessOfMatcher] # def validate_uniqueness_of(attr) ValidateUniquenessOfMatcher.new(attr) end - class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc: + # @private + class ValidateUniquenessOfMatcher < ValidationMatcher include Helpers def initialize(attribute) diff --git a/lib/shoulda/matchers/active_model/validation_matcher.rb b/lib/shoulda/matchers/active_model/validation_matcher.rb index a35df6a05..0ddf8f4ec 100644 --- a/lib/shoulda/matchers/active_model/validation_matcher.rb +++ b/lib/shoulda/matchers/active_model/validation_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveModel # :nodoc: - class ValidationMatcher # :nodoc: + module ActiveModel + # @private + class ValidationMatcher attr_reader :failure_message alias failure_message_for_should failure_message diff --git a/lib/shoulda/matchers/active_model/validation_message_finder.rb b/lib/shoulda/matchers/active_model/validation_message_finder.rb index d36d9754b..13e4a497f 100644 --- a/lib/shoulda/matchers/active_model/validation_message_finder.rb +++ b/lib/shoulda/matchers/active_model/validation_message_finder.rb @@ -1,8 +1,7 @@ module Shoulda module Matchers module ActiveModel - - # Finds message information from a model's #errors method. + # @private class ValidationMessageFinder include Helpers diff --git a/lib/shoulda/matchers/active_record.rb b/lib/shoulda/matchers/active_record.rb index 04d3ab116..0c27e8797 100644 --- a/lib/shoulda/matchers/active_record.rb +++ b/lib/shoulda/matchers/active_record.rb @@ -1,4 +1,5 @@ require 'shoulda/matchers/active_record/association_matcher' +require 'shoulda/matchers/active_record/association_matchers' require 'shoulda/matchers/active_record/association_matchers/counter_cache_matcher' require 'shoulda/matchers/active_record/association_matchers/inverse_of_matcher' require 'shoulda/matchers/active_record/association_matchers/order_matcher' @@ -16,18 +17,6 @@ module Shoulda module Matchers - # = Matchers for your active record models - # - # These matchers will test the associations for your - # ActiveRecord models. - # - # describe User do - # it { should have_one(:profile) } - # it { should have_many(:dogs) } - # it { should have_many(:messes).through(:dogs) } - # it { should belong_to(:lover) } - # end - # module ActiveRecord end end diff --git a/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb b/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb index 44f39f6d5..96db81fca 100644 --- a/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +++ b/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb @@ -1,26 +1,100 @@ module Shoulda module Matchers module ActiveRecord - # Ensures that the model can accept nested attributes for the specified - # association. - # - # Options: - # * allow_destroy - Whether or not to allow destroy - # * limit - Max number of nested attributes - # * update_only - Only allow updates - # - # Example: - # it { should accept_nested_attributes_for(:friends) } - # it { should accept_nested_attributes_for(:friends). - # allow_destroy(true). - # limit(4) } - # it { should accept_nested_attributes_for(:friends). - # update_only(true) } + # The `accept_nested_attributes_for` matcher tests usage of the + # `accepts_nested_attributes_for` macro. + # + # class Car < ActiveRecord::Base + # accept_nested_attributes_for :doors + # end + # + # # RSpec + # describe Car do + # it { should accept_nested_attributes_for(:doors) } + # end + # + # # Test::Unit (using Shoulda) + # class CarTest < ActiveSupport::TestCase + # should accept_nested_attributes_for(:doors) + # end + # + # #### Qualifiers + # + # ##### allow_destroy + # + # Use `allow_destroy` to assert that the `:allow_destroy` option was + # specified. + # + # class Car < ActiveRecord::Base + # accept_nested_attributes_for :mirrors, allow_destroy: true + # end + # + # # RSpec + # describe Car do + # it do + # should accept_nested_attributes_for(:mirrors). + # allow_destroy(true) + # end + # end + # + # # Test::Unit + # class CarTest < ActiveSupport::TestCase + # should accept_nested_attributes_for(:mirrors). + # allow_destroy(true) + # end + # + # ##### limit + # + # Use `limit` to assert that the `:limit` option was specified. + # + # class Car < ActiveRecord::Base + # accept_nested_attributes_for :windows, limit: 3 + # end + # + # # RSpec + # describe Car do + # it do + # should accept_nested_attributes_for(:windows). + # limit(3) + # end + # end + # + # # Test::Unit + # class CarTest < ActiveSupport::TestCase + # should accept_nested_attributes_for(:windows). + # limit(3) + # end + # + # ##### update_only + # + # Use `update_only` to assert that the `:update_only` option was + # specified. + # + # class Car < ActiveRecord::Base + # accept_nested_attributes_for :engine, update_only: true + # end + # + # # RSpec + # describe Car do + # it do + # should accept_nested_attributes_for(:engine). + # update_only(true) + # end + # end + # + # # Test::Unit + # class CarTest < ActiveSupport::TestCase + # should accept_nested_attributes_for(:engine). + # update_only(true) + # end + # + # @return [AcceptNestedAttributesForMatcher] # def accept_nested_attributes_for(name) AcceptNestedAttributesForMatcher.new(name) end + # @private class AcceptNestedAttributesForMatcher def initialize(name) @name = name diff --git a/lib/shoulda/matchers/active_record/association_matcher.rb b/lib/shoulda/matchers/active_record/association_matcher.rb index af8324f96..21c3059b9 100644 --- a/lib/shoulda/matchers/active_record/association_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matcher.rb @@ -1,95 +1,751 @@ require 'active_support/core_ext/module/delegation' -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: - # Ensure that the belongs_to relationship exists. - # - # Options: - # * class_name - tests that the association resolves to class_name. - # * validate - tests that the association makes use of the validate - # option. - # * touch - tests that the association makes use of the touch - # option. - # - # Example: - # it { should belong_to(:parent) } - # it { should belong_to(:parent).touch } - # it { should belong_to(:parent).validate } - # it { should belong_to(:parent).class_name("ModelClassName") } + module ActiveRecord + # The `belong_to` matcher is used to ensure that a `belong_to` association + # exists on your model. + # + # class Person < ActiveRecord::Base + # belongs_to :organization + # end + # + # # RSpec + # describe Person do + # it { should belong_to(:organization) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:organization) + # end + # + # Note that polymorphic associations are automatically detected and do not + # need any qualifiers: + # + # class Comment < ActiveRecord::Base + # belongs_to :commentable, polymorphic: true + # end + # + # # RSpec + # describe Comment do + # it { should belong_to(:commentable) } + # end + # + # # Test::Unit + # class CommentTest < ActiveSupport::TestCase + # should belong_to(:commentable) + # end + # + # #### Qualifiers + # + # ##### conditions + # + # Use `conditions` if your association is defined with a scope that sets + # the `where` clause. + # + # class Person < ActiveRecord::Base + # belongs_to :family, -> { where(everyone_is_perfect: false) } + # end + # + # # RSpec + # describe Person do + # it do + # should belong_to(:family). + # conditions(everyone_is_perfect: false) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:family). + # conditions(everyone_is_perfect: false) + # end + # + # ##### order + # + # Use `order` if your association is defined with a scope that sets the + # `order` clause. + # + # class Person < ActiveRecord::Base + # belongs_to :previous_company, -> { order('hired_on desc') } + # end + # + # # RSpec + # describe Person do + # it { should belong_to(:previous_company).order('hired_on desc') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:previous_company).order('hired_on desc') + # end + # + # ##### class_name + # + # Use `class_name` to test usage of the `:class_name` option. This + # asserts that the model you're referring to actually exists. + # + # class Person < ActiveRecord::Base + # belongs_to :ancient_city, class_name: 'City' + # end + # + # # RSpec + # describe Person do + # it { should belong_to(:ancient_city).class_name('City') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:ancient_city).class_name('City') + # end + # + # ##### with_foreign_key + # + # Use `with_foreign_key` to test usage of the `:foreign_key` option. + # + # class Person < ActiveRecord::Base + # belongs_to :great_country, foreign_key: 'country_id' + # end + # + # # RSpec + # describe Person do + # it do + # should belong_to(:great_country). + # with_foreign_key('country_id') + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:great_country). + # with_foreign_key('country_id') + # end + # + # ##### dependent + # + # Use `dependent` to assert that the `:dependent` option was specified. + # + # class Person < ActiveRecord::Base + # belongs_to :world, dependent: :destroy + # end + # + # # RSpec + # describe Person do + # it { should belong_to(:world).dependent(:destroy) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:world).dependent(:destroy) + # end + # + # ##### counter_cache + # + # Use `counter_cache` to assert that the `:counter_cache` option was + # specified. + # + # class Person < ActiveRecord::Base + # belongs_to :organization, counter_cache: true + # end + # + # # RSpec + # describe Person do + # it { should belong_to(:organization).counter_cache(true) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:organization).counter_cache(true) + # end + # + # ##### touch + # + # Use `touch` to assert that the `:touch` option was specified. + # + # class Person < ActiveRecord::Base + # belongs_to :organization, touch: true + # end + # + # # RSpec + # describe Person do + # it { should belong_to(:organization).touch(true) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:organization).touch(true) + # end + # + # #### autosave + # + # Use `autosave` to assert that the `:autosave` option was specified. + # + # class Account < ActiveRecord::Base + # belongs_to :bank, autosave: true + # end + # + # # RSpec + # describe Account do + # it { should belong_to(:bank).autosave(true) } + # end + # + # # Test::Unit + # class AccountTest < ActiveSupport::TestCase + # should belong_to(:bank).autosave(true) + # end + # + # ##### inverse_of + # + # Use `inverse_of` to assert that the `:inverse_of` option was specified. + # + # class Person < ActiveRecord::Base + # belongs_to :organization, inverse_of: :employees + # end + # + # # RSpec + # describe Person + # it { should belong_to(:organization).inverse_of(:employees) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should belong_to(:organization).inverse_of(:employees) + # end + # + # @return [AssociationMatcher] # def belong_to(name) AssociationMatcher.new(:belongs_to, name) end - # Ensures that the has_many relationship exists. Will also test that the - # associated table has the required columns. Works with polymorphic - # associations. - # - # Options: - # * through - association name for has_many :through - # * dependent - tests that the association makes use of the - # dependent option. - # * class_name - tests that the association resoves to class_name. - # * autosave - tests that the association makes use of the - # autosave option. - # * validate - tests that the association makes use of the validate - # option. - # - # Example: - # it { should have_many(:friends) } - # it { should have_many(:enemies).through(:friends) } - # it { should have_many(:enemies).dependent(:destroy) } - # it { should have_many(:friends).autosave } - # it { should have_many(:friends).validate } - # it { should have_many(:friends).class_name("Friend") } + # The `have_many` matcher is used to test that a `has_many` or `has_many + # :through` association exists on your model. + # + # class Person < ActiveRecord::Base + # has_many :friends + # end + # + # # RSpec + # describe Person do + # it { should have_many(:friends) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:friends) + # end + # + # #### Qualifiers + # + # ##### conditions + # + # Use `conditions` if your association is defined with a scope that sets + # the `where` clause. + # + # class Person < ActiveRecord::Base + # has_many :coins, -> { where(quality: 'mint') } + # end + # + # # RSpec + # describe Person do + # it { should have_many(:coins).conditions(quality: 'mint') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:coins).conditions(quality: 'mint') + # end + # + # ##### order + # + # Use `order` if your association is defined with a scope that sets the + # `order` clause. + # + # class Person < ActiveRecord::Base + # has_many :shirts, -> { order('color') } + # end + # + # # RSpec + # describe Person do + # it { should have_many(:shirts).order('color') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:shirts).order('color') + # end + # + # ##### class_name + # + # Use `class_name` to test usage of the `:class_name` option. This + # asserts that the model you're referring to actually exists. + # + # class Person < ActiveRecord::Base + # has_many :hopes, class_name: 'Dream' + # end + # + # # RSpec + # describe Person do + # it { should have_many(:hopes).class_name('Dream') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:hopes).class_name('Dream') + # end + # + # ##### with_foreign_key + # + # Use `with_foreign_key` to test usage of the `:foreign_key` option. + # + # class Person < ActiveRecord::Base + # has_many :worries, foreign_key: 'worrier_id' + # end + # + # # RSpec + # describe Person do + # it { should have_many(:worries).with_foreign_key('worrier_id') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:worries).with_foreign_key('worrier_id') + # end + # + # ##### dependent + # + # Use `dependent` to assert that the `:dependent` option was specified. + # + # class Person < ActiveRecord::Base + # has_many :secret_documents, dependent: :destroy + # end + # + # # RSpec + # describe Person do + # it { should have_many(:secret_documents).dependent(:destroy) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:secret_documents).dependent(:destroy) + # end + # + # ##### through + # + # Use `through` to test usage of the `:through` option. This asserts that + # the association you are going through actually exists. + # + # class Person < ActiveRecord::Base + # has_many :acquaintances, through: :friends + # end + # + # # RSpec + # describe Person do + # it { should have_many(:acquaintances).through(:friends) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:acquaintances).through(:friends) + # end + # + # ##### source + # + # Use `source` to test usage of the `:source` option on a `:through` + # association. + # + # class Person < ActiveRecord::Base + # has_many :job_offers, through: :friends, source: :opportunities + # end + # + # # RSpec + # describe Person do + # it do + # should have_many(:job_offers). + # through(:friends). + # source(:opportunities) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:job_offers). + # through(:friends). + # source(:opportunities) + # end + # + # ##### validate + # + # Use `validate` to assert that the `:validate` option was specified. + # + # class Person < ActiveRecord::Base + # has_many :ideas, validate: false + # end + # + # # RSpec + # describe Person do + # it { should have_many(:ideas).validate(false) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_many(:ideas).validate(false) + # end + # + # #### autosave + # + # Use `autosave` to assert that the `:autosave` option was specified. + # + # class Player < ActiveRecord::Base + # has_many :games, autosave: true + # end + # + # # RSpec + # describe Player do + # it { should have_many(:games).autosave(true) } + # end + # + # # Test::Unit + # class PlayerTest < ActiveSupport::TestCase + # should have_many(:games).autosave(true) + # end + # + # @return [AssociationMatcher] # def have_many(name) AssociationMatcher.new(:has_many, name) end - # Ensure that the has_one relationship exists. Will also test that the - # associated table has the required columns. Works with polymorphic - # associations. - # - # Options: - # * dependent - tests that the association makes use of the - # dependent option. - # * class_name - tests that the association resolves to class_name. - # * autosave - tests that the association makes use of the - # autosave option. - # * validate - tests that the association makes use of the validate - # option. - # - # Example: - # it { should have_one(:god) } # unless hindu - # it { should have_one(:god).dependent } - # it { should have_one(:god).autosave } - # it { should have_one(:god).validate } - # it { should have_one(:god).class_name("JHVH1") } + # The `have_one` matcher is used to test that a `has_one` or `has_one + # :through` association exists on your model. + # + # class Person < ActiveRecord::Base + # has_one :partner + # end + # + # # RSpec + # describe Person do + # it { should have_one(:partner) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:partner) + # end + # + # #### Qualifiers + # + # ##### conditions + # + # Use `conditions` if your association is defined with a scope that sets + # the `where` clause. + # + # class Person < ActiveRecord::Base + # has_one :pet, -> { where('weight < 80') } + # end + # + # # RSpec + # describe Person do + # it { should have_one(:pet).conditions('weight < 80') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:pet).conditions('weight < 80') + # end + # + # ##### order + # + # Use `order` if your association is defined with a scope that sets the + # `order` clause. + # + # class Person < ActiveRecord::Base + # has_one :focus, -> { order('priority desc') } + # end + # + # # RSpec + # describe Person do + # it { should have_one(:focus).order('priority desc') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:focus).order('priority desc') + # end + # + # ##### class_name + # + # Use `class_name` to test usage of the `:class_name` option. This + # asserts that the model you're referring to actually exists. + # + # class Person < ActiveRecord::Base + # has_one :chance, class_name: 'Opportunity' + # end + # + # # RSpec + # describe Person do + # it { should have_one(:chance).class_name('Opportunity') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:chance).class_name('Opportunity') + # end + # + # ##### dependent + # + # Use `dependent` to test that the `:dependent` option was specified. + # + # class Person < ActiveRecord::Base + # has_one :contract, dependent: :nullify + # end + # + # # RSpec + # describe Person do + # it { should have_one(:contract).dependent(:nullify) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:contract).dependent(:nullify) + # end + # + # ##### with_foreign_key + # + # Use `with_foreign_key` to test usage of the `:foreign_key` option. + # + # class Person < ActiveRecord::Base + # has_one :job, foreign_key: 'worker_id' + # end + # + # # RSpec + # describe Person do + # it { should have_one(:job).with_foreign_key('worker_id') } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:job).with_foreign_key('worker_id') + # end + # + # ##### through + # + # Use `through` to test usage of the `:through` option. This asserts that + # the association you are going through actually exists. + # + # class Person < ActiveRecord::Base + # has_one :life, through: :partner + # end + # + # # RSpec + # describe Person do + # it { should have_one(:life).through(:partner) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:life).through(:partner) + # end + # + # ##### source + # + # Use `source` to test usage of the `:source` option on a `:through` + # association. + # + # class Person < ActiveRecord::Base + # has_one :car, through: :partner, source: :vehicle + # end + # + # # RSpec + # describe Person do + # it { should have_one(:car).through(:partner).source(:vehicle) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:car).through(:partner).source(:vehicle) + # end + # + # ##### validate + # + # Use `validate` to assert that the the `:validate` option was specified. + # + # class Person < ActiveRecord::Base + # has_one :parking_card, validate: false + # end + # + # # RSpec + # describe Person do + # it { should have_one(:parking_card).validate(false) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_one(:parking_card).validate(false) + # end + # + # #### autosave + # + # Use `autosave` to assert that the `:autosave` option was specified. + # + # class Account < ActiveRecord::Base + # has_one :bank, autosave: true + # end + # + # # RSpec + # describe Account do + # it { should have_one(:bank).autosave(true) } + # end + # + # # Test::Unit + # class AccountTest < ActiveSupport::TestCase + # should have_one(:bank).autosave(true) + # end + # + # @return [AssociationMatcher] # def have_one(name) AssociationMatcher.new(:has_one, name) end - # Ensures that the has_and_belongs_to_many relationship exists, and that - # the join table is in place. + # The `have_and_belong_to_many` matcher is used to test that a + # `has_and_belongs_to_many` association exists on your model and that the + # join table exists in the database. + # + # class Person < ActiveRecord::Base + # has_and_belongs_to_many :awards + # end + # + # # RSpec + # describe Person do + # it { should have_and_belong_to_many(:awards) } + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_and_belong_to_many(:awards) + # end + # + # #### Qualifiers + # + # ##### conditions + # + # Use `conditions` if your association is defined with a scope that sets + # the `where` clause. + # + # class Person < ActiveRecord::Base + # has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') } + # end + # + # # RSpec + # describe Person do + # it do + # should have_and_belong_to_many(:issues). + # conditions(difficulty: 'hard') + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_and_belong_to_many(:issues). + # conditions(difficulty: 'hard') + # end + # + # ##### order + # + # Use `order` if your association is defined with a scope that sets the + # `order` clause. + # + # class Person < ActiveRecord::Base + # has_and_belongs_to_many :projects, -> { order('time_spent') } + # end + # + # # RSpec + # describe Person do + # it do + # should have_and_belong_to_many(:projects). + # order('time_spent') + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_and_belong_to_many(:projects). + # order('time_spent') + # end + # + # ##### class_name + # + # Use `class_name` to test usage of the `:class_name` option. This + # asserts that the model you're referring to actually exists. + # + # class Person < ActiveRecord::Base + # has_and_belongs_to_many :places_visited, class_name: 'City' + # end + # + # # RSpec + # describe Person do + # it do + # should have_and_belong_to_many(:places_visited). + # class_name('City') + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_and_belong_to_many(:places_visited). + # class_name('City') + # end + # + # ##### validate + # + # Use `validate` to test that the `:validate` option was specified. + # + # class Person < ActiveRecord::Base + # has_and_belongs_to_many :interviews, validate: false + # end + # + # # RSpec + # describe Person do + # it do + # should have_and_belong_to_many(:interviews). + # validate(false) + # end + # end + # + # # Test::Unit + # class PersonTest < ActiveSupport::TestCase + # should have_and_belong_to_many(:interviews). + # validate(false) + # end + # + # #### autosave + # + # Use `autosave` to assert that the `:autosave` option was specified. + # + # class Publisher < ActiveRecord::Base + # has_and_belongs_to_many :advertisers, autosave: true + # end + # + # # RSpec + # describe Publisher do + # it { should have_and_belong_to_many(:advertisers).autosave(true) } + # end # - # Options: - # * class_name - tests that the association resolves to class_name. - # * validate - tests that the association makes use of the validate - # option. + # # Test::Unit + # class AccountTest < ActiveSupport::TestCase + # should have_and_belong_to_many(:advertisers).autosave(true) + # end # - # Example: - # it { should have_and_belong_to_many(:posts) } - # it { should have_and_belong_to_many(:posts).validate } - # it { should have_and_belong_to_many(:posts).class_name("Post") } + # @return [AssociationMatcher] # def have_and_belong_to_many(name) AssociationMatcher.new(:has_and_belongs_to_many, name) end - class AssociationMatcher # :nodoc: + # @private + class AssociationMatcher delegate :reflection, :model_class, :associated_class, :through?, :join_table, :polymorphic?, to: :reflector diff --git a/lib/shoulda/matchers/active_record/association_matchers.rb b/lib/shoulda/matchers/active_record/association_matchers.rb new file mode 100644 index 000000000..802cb59bc --- /dev/null +++ b/lib/shoulda/matchers/active_record/association_matchers.rb @@ -0,0 +1,9 @@ +module Shoulda + module Matchers + module ActiveRecord + # @private + module AssociationMatchers + end + end + end +end diff --git a/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb index 761e97af7..f124752ec 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class CounterCacheMatcher attr_accessor :missing_option diff --git a/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb index ac5adecdc..bff159f71 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class DependentMatcher attr_accessor :missing_option diff --git a/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb index e6021c275..639cadc20 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class InverseOfMatcher attr_accessor :missing_option diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb index 62394b5a9..fe3bee591 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb @@ -4,6 +4,7 @@ module Shoulda module Matchers module ActiveRecord module AssociationMatchers + # @private class ModelReflection < SimpleDelegator def initialize(reflection) super(reflection) diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb index 490209969..7fb477a3e 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class ModelReflector delegate :associated_class, :through?, :join_table, :association_relation, :polymorphic?, to: :reflection diff --git a/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb b/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb index 3cb384960..e9ddc2350 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class OptionVerifier delegate :reflection, to: :reflector diff --git a/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb index 53246baab..0667c91dd 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class OrderMatcher attr_accessor :missing_option diff --git a/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb index 672c76ea5..6f7dffb69 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class SourceMatcher attr_accessor :missing_option diff --git a/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb index c92dea4fd..d7b163083 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb @@ -1,7 +1,8 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: + module ActiveRecord module AssociationMatchers + # @private class ThroughMatcher attr_accessor :missing_option diff --git a/lib/shoulda/matchers/active_record/have_db_column_matcher.rb b/lib/shoulda/matchers/active_record/have_db_column_matcher.rb index 4b00e6065..bd519ea60 100644 --- a/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +++ b/lib/shoulda/matchers/active_record/have_db_column_matcher.rb @@ -1,25 +1,89 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: - - # Ensures the database column exists. + module ActiveRecord + # The `have_db_column` matcher tests that the table that backs your model + # has a specific column. + # + # class CreatePhones < ActiveRecord::Migration + # def change + # create_table :phones do |t| + # t.string :supported_ios_version + # end + # end + # end + # + # # RSpec + # describe Phone do + # it { should have_db_column(:supported_ios_version) } + # end + # + # # Test::Unit + # class PhoneTest < ActiveSupport::TestCase + # should have_db_column(:supported_ios_version) + # end + # + # #### Qualifiers + # + # ##### of_type + # + # Use `of_type` to assert that a column is defined as a certain type. + # + # class CreatePhones < ActiveRecord::Migration + # def change + # create_table :phones do |t| + # t.decimal :camera_aperture + # end + # end + # end + # + # # RSpec + # describe Phone do + # it do + # should have_db_column(:camera_aperture).of_type(:decimal) + # end + # end + # + # # Test::Unit + # class PhoneTest < ActiveSupport::TestCase + # should have_db_column(:camera_aperture).of_type(:decimal) + # end + # + # ##### with_options + # + # Use `with_options` to assert that a column has been defined with + # certain options (`:precision`, `:limit`, `:default`, `:null`, `:scale`, + # or `:primary`). + # + # class CreatePhones < ActiveRecord::Migration + # def change + # create_table :phones do |t| + # t.decimal :camera_aperture, precision: 1, null: false + # end + # end + # end + # + # # RSpec + # describe Phone do + # it do + # should have_db_column(:camera_aperture). + # with_options(precision: 1, null: false) + # end + # end # - # Options: - # * of_type - db column type (:integer, :string, etc.) - # * with_options - same options available in migrations - # (:default, :null, :limit, :precision, :scale) + # # Test::Unit + # class PhoneTest < ActiveSupport::TestCase + # should have_db_column(:camera_aperture). + # with_options(precision: 1, null: false) + # end # - # Examples: - # it { should_not have_db_column(:admin).of_type(:boolean) } - # it { should have_db_column(:salary). - # of_type(:decimal). - # with_options(precision: 10, scale: 2) } + # @return [HaveDbColumnMatcher] # def have_db_column(column) HaveDbColumnMatcher.new(column) end - class HaveDbColumnMatcher # :nodoc: + # @private + class HaveDbColumnMatcher def initialize(column) @column = column @options = {} diff --git a/lib/shoulda/matchers/active_record/have_db_index_matcher.rb b/lib/shoulda/matchers/active_record/have_db_index_matcher.rb index a08aafd0f..6868bb224 100644 --- a/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +++ b/lib/shoulda/matchers/active_record/have_db_index_matcher.rb @@ -1,27 +1,76 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: - - # Ensures that there are DB indices on the given columns or tuples of - # columns. + module ActiveRecord + # The `have_db_index` matcher tests that the table that backs your model + # has a index on a specific column. + # + # class CreateBlogs < ActiveRecord::Migration + # def change + # create_table :blogs do |t| + # t.integer :user_id + # end + # + # add_index :blogs, :user_id + # end + # end + # + # # RSpec + # describe Blog do + # it { should have_db_index(:user_id) } + # end + # + # # Test::Unit + # class BlogTest < ActiveSupport::TestCase + # should have_db_index(:user_id) + # end + # + # #### Qualifiers + # + # ##### unique + # + # Use `unique` to assert that the index is unique. + # + # class CreateBlogs < ActiveRecord::Migration + # def change + # create_table :blogs do |t| + # t.string :name + # end + # + # add_index :blogs, :name, unique: true + # end + # end + # + # # RSpec + # describe Blog do + # it { should have_db_index(:name).unique(true) } + # end + # + # # Test::Unit + # class BlogTest < ActiveSupport::TestCase + # should have_db_index(:name).unique(true) + # end + # + # Since it only ever makes since for `unique` to be `true`, you can also + # leave off the argument to save some keystrokes: # - # Options: - # * unique - whether or not the index has a unique - # constraint. Use true to explicitly test for a unique - # constraint. Use false to explicitly test for a non-unique - # constraint. + # # RSpec + # describe Blog do + # it { should have_db_index(:name).unique } + # end # - # Examples: + # # Test::Unit + # class BlogTest < ActiveSupport::TestCase + # should have_db_index(:name).unique + # end # - # it { should have_db_index(:age) } - # it { should have_db_index([:commentable_type, :commentable_id]) } - # it { should have_db_index(:ssn).unique(true) } + # @return [HaveDbIndexMatcher] # def have_db_index(columns) HaveDbIndexMatcher.new(columns) end - class HaveDbIndexMatcher # :nodoc: + # @private + class HaveDbIndexMatcher def initialize(columns) @columns = normalize_columns_to_array(columns) @options = {} diff --git a/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb b/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb index fbd7db1a3..ea4e68aab 100644 --- a/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +++ b/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb @@ -1,17 +1,31 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: - - # Ensures that the attribute cannot be changed once the record has been - # created. + module ActiveRecord + # The `have_readonly_attribute` matcher tests usage of the + # `attr_readonly` macro. + # + # class User < ActiveRecord::Base + # attr_readonly :password + # end + # + # # RSpec + # describe User do + # it { should have_readonly_attribute(:password) } + # end + # + # # Test::Unit + # class UserTest < ActiveSupport::TestCase + # should have_readonly_attribute(:password) + # end # - # it { should have_readonly_attribute(:password) } + # @return [HaveReadonlyAttributeMatcher] # def have_readonly_attribute(value) HaveReadonlyAttributeMatcher.new(value) end - class HaveReadonlyAttributeMatcher # :nodoc: + # @private + class HaveReadonlyAttributeMatcher def initialize(attribute) @attribute = attribute.to_s end diff --git a/lib/shoulda/matchers/active_record/serialize_matcher.rb b/lib/shoulda/matchers/active_record/serialize_matcher.rb index 10e616aa4..863b74791 100644 --- a/lib/shoulda/matchers/active_record/serialize_matcher.rb +++ b/lib/shoulda/matchers/active_record/serialize_matcher.rb @@ -1,21 +1,96 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module ActiveRecord # :nodoc: - # Ensure that the field becomes serialized. + module ActiveRecord + # The `serialize` matcher tests usage of the `serialize` macro. # - # Options: - # * :as - tests that the serialized attribute makes use of the class_name option. + # class Product < ActiveRecord::Base + # serialize :customizations + # end # - # Example: - # it { should serialize(:details) } - # it { should serialize(:details).as(Hash) } - # it { should serialize(:details).as_instance_of(ExampleSerializer) } + # # RSpec + # describe Product do + # it { should serialize(:customizations) } + # end + # + # # Test::Unit + # class ProductTest < ActiveSupport::TestCase + # should serialize(:customizations) + # end + # + # #### Qualifiers + # + # ##### as + # + # Use `as` if you are using a custom serializer class. + # + # class ProductSpecsSerializer + # def load(string) + # # ... + # end + # + # def dump(options) + # # ... + # end + # end + # + # class Product < ActiveRecord::Base + # serialize :specifications, ProductSpecsSerializer + # end + # + # # RSpec + # describe Product do + # it do + # should serialize(:specifications). + # as(ProductSpecsSerializer) + # end + # end + # + # # Test::Unit + # class ProductTest < ActiveSupport::TestCase + # should serialize(:specifications). + # as(ProductSpecsSerializer) + # end + # + # ##### as_instance_of + # + # Use `as_instance_of` if you are using a custom serializer object. + # + # class ProductOptionsSerializer + # def load(string) + # # ... + # end + # + # def dump(options) + # # ... + # end + # end + # + # class Product < ActiveRecord::Base + # serialize :options, ProductOptionsSerializer.new + # end + # + # # RSpec + # describe Product do + # it do + # should serialize(:options). + # as_instance_of(ProductOptionsSerializer) + # end + # end + # + # # Test::Unit + # class ProductTest < ActiveSupport::TestCase + # should serialize(:options). + # as_instance_of(ProductOptionsSerializer) + # end + # + # @return [SerializeMatcher] # def serialize(name) SerializeMatcher.new(name) end - class SerializeMatcher # :nodoc: + # @private + class SerializeMatcher def initialize(name) @name = name.to_s @options = {} diff --git a/lib/shoulda/matchers/assertion_error.rb b/lib/shoulda/matchers/assertion_error.rb index c1a09c354..969081cbb 100644 --- a/lib/shoulda/matchers/assertion_error.rb +++ b/lib/shoulda/matchers/assertion_error.rb @@ -2,9 +2,11 @@ module Shoulda module Matchers if Gem.ruby_version >= Gem::Version.new('1.8') && Gem.ruby_version < Gem::Version.new('1.9') require 'test/unit' + # @private AssertionError = Test::Unit::AssertionFailedError elsif defined?(Test::Unit::AssertionFailedError) # Test::Unit has been loaded already, so we use it + # @private AssertionError = Test::Unit::AssertionFailedError elsif Gem.ruby_version >= Gem::Version.new("1.9") begin @@ -12,6 +14,7 @@ module Matchers rescue LoadError require 'minitest/unit' ensure + # @private AssertionError = MiniTest::Assertion end else diff --git a/lib/shoulda/matchers/doublespeak.rb b/lib/shoulda/matchers/doublespeak.rb index 4f5437ca6..9663e1fa2 100644 --- a/lib/shoulda/matchers/doublespeak.rb +++ b/lib/shoulda/matchers/doublespeak.rb @@ -2,6 +2,7 @@ module Shoulda module Matchers + # @private module Doublespeak class << self extend Forwardable diff --git a/lib/shoulda/matchers/doublespeak/double.rb b/lib/shoulda/matchers/doublespeak/double.rb index 1f47fe0dd..7fd7d2c8a 100644 --- a/lib/shoulda/matchers/doublespeak/double.rb +++ b/lib/shoulda/matchers/doublespeak/double.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private class Double attr_reader :calls diff --git a/lib/shoulda/matchers/doublespeak/double_collection.rb b/lib/shoulda/matchers/doublespeak/double_collection.rb index 8c36cca38..0c958cc3f 100644 --- a/lib/shoulda/matchers/doublespeak/double_collection.rb +++ b/lib/shoulda/matchers/doublespeak/double_collection.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private class DoubleCollection def initialize(klass) @klass = klass diff --git a/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb b/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb index 1f05a5a62..71e43e1de 100644 --- a/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +++ b/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private module DoubleImplementationRegistry class << self REGISTRY = {} diff --git a/lib/shoulda/matchers/doublespeak/object_double.rb b/lib/shoulda/matchers/doublespeak/object_double.rb index 450eedb39..6789ca600 100644 --- a/lib/shoulda/matchers/doublespeak/object_double.rb +++ b/lib/shoulda/matchers/doublespeak/object_double.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private class ObjectDouble < BasicObject attr_reader :calls diff --git a/lib/shoulda/matchers/doublespeak/proxy_implementation.rb b/lib/shoulda/matchers/doublespeak/proxy_implementation.rb index 398629564..80460c484 100644 --- a/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +++ b/lib/shoulda/matchers/doublespeak/proxy_implementation.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private class ProxyImplementation extend Forwardable diff --git a/lib/shoulda/matchers/doublespeak/structs.rb b/lib/shoulda/matchers/doublespeak/structs.rb index 22739008f..c7b2ed2dd 100644 --- a/lib/shoulda/matchers/doublespeak/structs.rb +++ b/lib/shoulda/matchers/doublespeak/structs.rb @@ -1,7 +1,9 @@ module Shoulda module Matchers module Doublespeak + # @private MethodCall = Struct.new(:args, :block) + # @private MethodCallWithName = Struct.new(:method_name, :args, :block) end end diff --git a/lib/shoulda/matchers/doublespeak/stub_implementation.rb b/lib/shoulda/matchers/doublespeak/stub_implementation.rb index 01e112644..18ec7f4a0 100644 --- a/lib/shoulda/matchers/doublespeak/stub_implementation.rb +++ b/lib/shoulda/matchers/doublespeak/stub_implementation.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private class StubImplementation DoubleImplementationRegistry.register(self, :stub) diff --git a/lib/shoulda/matchers/doublespeak/world.rb b/lib/shoulda/matchers/doublespeak/world.rb index ae272aa27..cb2960103 100644 --- a/lib/shoulda/matchers/doublespeak/world.rb +++ b/lib/shoulda/matchers/doublespeak/world.rb @@ -1,6 +1,7 @@ module Shoulda module Matchers module Doublespeak + # @private class World def register_double_collection(klass) double_collection = DoubleCollection.new(klass) diff --git a/lib/shoulda/matchers/error.rb b/lib/shoulda/matchers/error.rb index bf8206212..2ec38b14c 100644 --- a/lib/shoulda/matchers/error.rb +++ b/lib/shoulda/matchers/error.rb @@ -1,5 +1,6 @@ module Shoulda module Matchers + # @private class Error < StandardError def self.create(attributes) allocate.tap do |error| diff --git a/lib/shoulda/matchers/independent/delegate_matcher.rb b/lib/shoulda/matchers/independent/delegate_matcher.rb index 1637f9496..b2be7042d 100644 --- a/lib/shoulda/matchers/independent/delegate_matcher.rb +++ b/lib/shoulda/matchers/independent/delegate_matcher.rb @@ -1,30 +1,117 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module Independent # :nodoc: - - # Ensure that a given method is delegated properly. + module Independent + # The `delegate_method` matcher tests that an object forwards messages + # to other, internal objects by way of delegation. + # + # In this example, we test that Courier forwards a call to #deliver onto + # its PostOffice instance: + # + # require 'forwardable' + # + # class Courier + # extend Forwardable + # + # attr_reader :post_office + # + # def_delegators :post_office, :deliver + # + # def initialize + # @post_office = PostOffice.new + # end + # end + # + # # RSpec + # describe Courier do + # it { should delegate_method(:deliver).to(:post_office) } + # end + # + # # Test::Unit + # class CourierTest < Test::Unit::TestCase + # should delegate_method(:deliver).to(:post_office) + # end + # + # To employ some terminology, we would say that Courier's #deliver method + # is the delegating method, PostOffice is the delegate object, and + # PostOffice#deliver is the delegate method. + # + # #### Qualifiers + # + # ##### as + # + # Use `as` if the name of the delegate method is different from the name + # of the delegating method. + # + # Here, Courier has a #deliver method, but instead of calling #deliver on + # the PostOffice, it calls #ship: + # + # class Courier + # attr_reader :post_office + # + # def initialize + # @post_office = PostOffice.new + # end + # + # def deliver(package) + # post_office.ship(package) + # end + # end + # + # # RSpec + # describe Courier do + # it { should delegate_method(:deliver).to(:post_office).as(:ship) } + # end + # + # # Test::Unit + # class CourierTest < Test::Unit::TestCase + # should delegate_method(:deliver).to(:post_office).as(:ship) + # end + # + # ##### with_arguments + # + # Use `with_arguments` to assert that the delegate method is called with + # certain arguments. + # + # Here, when Courier#deliver calls PostOffice#ship, it adds an options + # hash: + # + # class Courier + # attr_reader :post_office + # + # def initialize + # @post_office = PostOffice.new + # end + # + # def deliver(package) + # post_office.ship(package, expedited: true) + # end + # end # - # Basic Syntax: - # it { should delegate_method(method_name).to(delegate_name) } + # # RSpec + # describe Courier do + # it do + # should delegate_method(:deliver). + # to(:post_office). + # as(:ship). + # with_arguments(expedited: true) + # end + # end # - # Options: - # * :as - The name of the delegating method. Defaults to - # method_name. - # * :with_arguments - Tests that the delegate method is called - # with certain arguments. + # # Test::Unit + # class CourierTest < Test::Unit::TestCase + # should delegate_method(:deliver). + # to(:post_office). + # as(:ship). + # with_arguments(expedited: true) + # end # - # Examples: - # it { should delegate_method(:deliver_mail).to(:mailman) } - # it { should delegate_method(:deliver_mail).to(:mailman). - # as(:deliver_mail_via_mailman) } - # it { should delegate_method(:deliver_mail).to(:mailman). - # as(:deliver_mail_hastily). - # with_arguments('221B Baker St.', hastily: true) } + # @return [DelegateMatcher] # def delegate_method(delegating_method) DelegateMatcher.new(delegating_method) end + # @private class DelegateMatcher def initialize(delegating_method) @delegating_method = delegating_method @@ -196,6 +283,7 @@ def formatted_calls_on_target string end + # @private class TargetNotDefinedError < StandardError def message 'Delegation needs a target. Use the #to method to define one, e.g. diff --git a/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb b/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb index cafbe7406..4a5c73a8c 100644 --- a/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb +++ b/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb @@ -1,6 +1,7 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - module Independent # :nodoc: + module Independent + # @private class DelegateMatcher::StubbedTarget # :nodoc: def initialize(method) @received_method = false diff --git a/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb b/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb index dabc1d567..ad87191f3 100644 --- a/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb +++ b/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb @@ -1,6 +1,8 @@ module Shoulda module Matchers + # @private module Integrations + # @private module NUnitTestCaseDetection def self.possible_test_case_constants [ @@ -29,6 +31,7 @@ def self.test_case_constants end end + # @private def self.nunit_test_case_constants Integrations::NUnitTestCaseDetection.test_case_constants end diff --git a/lib/shoulda/matchers/rails_shim.rb b/lib/shoulda/matchers/rails_shim.rb index cddca1fb6..3307a7666 100644 --- a/lib/shoulda/matchers/rails_shim.rb +++ b/lib/shoulda/matchers/rails_shim.rb @@ -1,6 +1,7 @@ -module Shoulda # :nodoc: +module Shoulda module Matchers - class RailsShim # :nodoc: + # @private + class RailsShim def self.layouts_ivar if action_pack_major_version >= 4 '@_layouts' diff --git a/lib/shoulda/matchers/version.rb b/lib/shoulda/matchers/version.rb index 4ab794b2d..567e7e975 100644 --- a/lib/shoulda/matchers/version.rb +++ b/lib/shoulda/matchers/version.rb @@ -1,5 +1,6 @@ module Shoulda module Matchers + # @private VERSION = '2.6.1'.freeze end end diff --git a/lib/shoulda/matchers/warn.rb b/lib/shoulda/matchers/warn.rb index 46796e2f1..3fd024f17 100644 --- a/lib/shoulda/matchers/warn.rb +++ b/lib/shoulda/matchers/warn.rb @@ -1,5 +1,6 @@ module Shoulda module Matchers + # @private def self.warn(msg) Kernel.warn "Warning from shoulda-matchers:\n\n#{msg}" end