1
0
Fork 0
mirror of https://github.com/thoughtbot/shoulda-matchers.git synced 2022-11-09 12:01:38 -05:00

Extract examples in README to inline documentation

This commit is contained in:
Elliot Winkler 2014-01-23 11:07:36 -07:00
parent ed428417a3
commit c22d7c89e0
73 changed files with 3577 additions and 1878 deletions

1389
README.md

File diff suppressed because it is too large Load diff

View file

@ -13,26 +13,6 @@ require 'shoulda/matchers/action_controller/strong_parameters_matcher'
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

View file

@ -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.
#
# Example:
# 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
#
# # 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.
#
# Example:
# 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
#
# @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.
#
# Example:
# 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
#
# # 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.
#
# Example:
# 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
#
# @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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.
#
# Example:
# 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:
#
# 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

View file

@ -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

View file

@ -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 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.
# You can specify that the status should be a number:
#
# Example:
# 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 }
#
# it { should respond_with(500..600) }
# end
# end
#
# # 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

View file

@ -1,36 +1,92 @@
module Shoulda # :nodoc:
module Shoulda
module Matchers
module ActionController # :nodoc:
# Ensures that requesting +path+ using +method+ routes to +options+.
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`.
#
# If you don't specify a controller, it will use the controller from the
# example group.
# You can use this matcher either in a controller test case or in a
# routing test case. For instance, given these routes:
#
# +to_param+ is called on the +options+ given.
# My::Application.routes.draw do
# get '/posts', controller: 'posts', action: 'index'
# get '/posts/:id' => 'posts#show'
# end
#
# Examples:
# 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]
#
# 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) }
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

View file

@ -1,6 +1,7 @@
module Shoulda # :nodoc:
module Shoulda
module Matchers
module ActionController # :nodoc:
module ActionController
# @private
class RouteParams
def initialize(args)
@args = args

View file

@ -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.
#
# Example:
# 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
#
# @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

View file

@ -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.
#
# Example:
# 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
#
# @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

View file

@ -10,10 +10,154 @@ require 'active_support/hash_with_indifferent_access'
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 @@ module Shoulda
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)'

View file

@ -22,33 +22,8 @@ require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
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

View file

@ -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.
#
# it { should_not allow_mass_assignment_of(:password) }
# it { should allow_mass_assignment_of(:first_name) }
# class Post
# include ActiveModel::Model
# include ActiveModel::MassAssignmentSecurity
# attr_accessor :title
#
# In Rails 3.1 you can check role as well:
# attr_accessible :title
# end
#
# it { should allow_mass_assignment_of(:first_name).as(:admin) }
# 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
#
# # RSpec
# describe Post do
# it { should allow_mass_assignment_of(:title).as(:admin) }
# end
#
# # Test::Unit
# class PostTest < ActiveSupport::TestCase
# should allow_mass_assignment_of(:title).as(:admin)
# end
#
# @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

View file

@ -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.
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. If omitted,
# the test looks for any errors in <tt>errors.on(:attribute)</tt>.
# * <tt>strict</tt> - 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.
# #### should
#
# Example:
# it { should_not allow_value('bad').for(:isbn) }
# it { should allow_value('isbn 1 2345 6789 0').for(:isbn) }
# 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
#
# # 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
#
# @return [AllowValueMatcher]
#
def allow_value(*values)
if values.empty?
@ -27,7 +168,8 @@ module Shoulda # :nodoc:
end
end
class AllowValueMatcher # :nodoc:
# @private
class AllowValueMatcher
include Helpers
attr_accessor :attribute_with_message

View file

@ -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

View file

@ -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.
#
# Options:
# * <tt>in_array</tt> - the array of not allowed values for this attribute
# * <tt>in_range</tt> - the range of not allowed values for this attribute
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for :exclusion.
# If your blacklist is an array of values, use `in_array`:
#
# Example:
# it { should ensure_exclusion_of(:age).in_range(30..60) }
# 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
#
# # 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
#
# @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

View file

@ -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.
#
# Options:
# * <tt>in_array</tt> - the array of allowed values for this attribute
# * <tt>in_range</tt> - the range of allowed values for this attribute
# * <tt>with_low_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for :inclusion.
# * <tt>with_high_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for :inclusion.
# If your whitelist is an array of values, use `in_array`:
#
# Example:
# it { should ensure_inclusion_of(:age).in_range(0..100) }
# 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
#
# # Test::Unit
# class IssueTest < ActiveSupport::TestCase
# should ensure_inclusion_of(:state).
# in_array(%w(open resolved unresolved)).
# allow_blank
# end
#
# @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')

View file

@ -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.
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.
#
# Options:
# * <tt>is_at_least</tt> - minimum length of this attribute
# * <tt>is_at_most</tt> - maximum length of this attribute
# * <tt>is_equal_to</tt> - exact requred length of this attribute
# * <tt>with_short_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for :too_short.
# * <tt>with_long_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for :too_long.
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for :wrong_length. Used in conjunction with
# <tt>is_equal_to</tt>.
# #### 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]
#
# 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/) }
def ensure_length_of(attr)
EnsureLengthOfMatcher.new(attr)
end
class EnsureLengthOfMatcher < ValidationMatcher # :nodoc:
# @private
class EnsureLengthOfMatcher < ValidationMatcher
include Helpers
def initialize(attribute)

View file

@ -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 @@ Hence, this test will fail and there is no way to make it pass.
end
end
# @private
class CouldNotSetPasswordError < Shoulda::Matchers::Error
def self.create(model)
super(model: model)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,9 @@
module Shoulda
module Matchers
module ActiveModel
# @private
module NumericalityMatchers
end
end
end
end

View file

@ -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,

View file

@ -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 = {})

View file

@ -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

View file

@ -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 = {})

View file

@ -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

View file

@ -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.
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
# Defaults to the translation for <tt>:present</tt>.
# 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
#
# 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

View file

@ -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.
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for <tt>:accepted</tt>.
# class Registration
# include ActiveModel::Model
# attr_accessor :eula
#
# Example:
# it { should validate_acceptance_of(: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
#
# ##### with_message
#
# 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

View file

@ -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

View file

@ -1,28 +1,284 @@
module Shoulda # :nodoc:
module Shoulda
module Matchers
module ActiveModel # :nodoc:
# Ensure that the attribute is numeric.
module ActiveModel
# The `validate_numericality_of` matcher tests usage of the
# `validates_numericality_of` validation.
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
# translation for <tt>:not_a_number</tt>.
# * <tt>only_integer</tt> - allows only integer values
# * <tt>odd</tt> - Specifies the value must be an odd number.
# * <tt>even</tt> - Specifies the value must be an even number.
# * <tt>allow_nil</tt> - allows nil values
# class Person
# include ActiveModel::Model
# attr_accessor :gpa
#
# 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 }
# 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'

View file

@ -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.
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
# Defaults to the translation for <tt>:blank</tt>.
# class Robot
# include ActiveModel::Model
# attr_accessor :arms
#
# Examples:
# it { should validate_presence_of(:name) }
# it { should validate_presence_of(:name).
# with_message(/is not optional/) }
# 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
#
# # Test::Unit
# class RobotTest < ActiveSupport::TestCase
# should validate_presence_of(:legs).
# with_message('Robot has no legs')
# end
#
# @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

View file

@ -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`.
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).
#
# Example:
# it { should validate_uniqueness_of(:email) }
# class Post < ActiveRecord::Base
# validates_uniqueness_of :permalink
# end
#
# Options:
# # RSpec
# describe Post do
# it { should validate_uniqueness_of(:permalink) }
# end
#
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
# Defaults to the translation for <tt>:taken</tt>.
# * <tt>scoped_to</tt> - field(s) to scope the uniqueness to.
# * <tt>case_insensitive</tt> - ensures that the validation does not
# check case. Off by default. Ignored by non-text attributes.
# # Test::Unit
# class PostTest < ActiveSupport::TestCase
# should validate_uniqueness_of(:permalink)
# end
#
# 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 }
# #### 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)

View file

@ -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

View file

@ -1,8 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
# Finds message information from a model's #errors method.
# @private
class ValidationMessageFinder
include Helpers

View file

@ -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 @@ require 'shoulda/matchers/active_record/accept_nested_attributes_for_matcher'
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

View file

@ -1,26 +1,100 @@
module Shoulda
module Matchers
module ActiveRecord
# Ensures that the model can accept nested attributes for the specified
# association.
# The `accept_nested_attributes_for` matcher tests usage of the
# `accepts_nested_attributes_for` macro.
#
# Options:
# * <tt>allow_destroy</tt> - Whether or not to allow destroy
# * <tt>limit</tt> - Max number of nested attributes
# * <tt>update_only</tt> - Only allow updates
# class Car < ActiveRecord::Base
# accept_nested_attributes_for :doors
# end
#
# 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) }
# # 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

View file

@ -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.
module ActiveRecord
# The `belong_to` matcher is used to ensure that a `belong_to` association
# exists on your model.
#
# Options:
# * <tt>class_name</tt> - tests that the association resolves to class_name.
# * <tt>validate</tt> - tests that the association makes use of the validate
# option.
# * <tt>touch</tt> - tests that the association makes use of the touch
# option.
# class Person < ActiveRecord::Base
# belongs_to :organization
# end
#
# 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") }
# # 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.
# The `have_many` matcher is used to test that a `has_many` or `has_many
# :through` association exists on your model.
#
# Options:
# * <tt>through</tt> - association name for <tt>has_many :through</tt>
# * <tt>dependent</tt> - tests that the association makes use of the
# dependent option.
# * <tt>class_name</tt> - tests that the association resoves to class_name.
# * <tt>autosave</tt> - tests that the association makes use of the
# autosave option.
# * <tt>validate</tt> - tests that the association makes use of the validate
# option.
# class Person < ActiveRecord::Base
# has_many :friends
# end
#
# 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") }
# # 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.
# The `have_one` matcher is used to test that a `has_one` or `has_one
# :through` association exists on your model.
#
# Options:
# * <tt>dependent</tt> - tests that the association makes use of the
# dependent option.
# * <tt>class_name</tt> - tests that the association resolves to class_name.
# * <tt>autosave</tt> - tests that the association makes use of the
# autosave option.
# * <tt>validate</tt> - tests that the association makes use of the validate
# option.
# class Person < ActiveRecord::Base
# has_one :partner
# end
#
# 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") }
# # 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.
#
# Options:
# * <tt>class_name</tt> - tests that the association resolves to class_name.
# * <tt>validate</tt> - tests that the association makes use of the validate
# option.
# class Person < ActiveRecord::Base
# has_and_belongs_to_many :awards
# 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") }
# # 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
#
# # Test::Unit
# class AccountTest < ActiveSupport::TestCase
# should have_and_belong_to_many(:advertisers).autosave(true)
# end
#
# @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

View file

@ -0,0 +1,9 @@
module Shoulda
module Matchers
module ActiveRecord
# @private
module AssociationMatchers
end
end
end
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -4,6 +4,7 @@ module Shoulda
module Matchers
module ActiveRecord
module AssociationMatchers
# @private
class ModelReflection < SimpleDelegator
def initialize(reflection)
super(reflection)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.
#
# Options:
# * <tt>of_type</tt> - db column type (:integer, :string, etc.)
# * <tt>with_options</tt> - same options available in migrations
# (:default, :null, :limit, :precision, :scale)
# class CreatePhones < ActiveRecord::Migration
# def change
# create_table :phones do |t|
# t.string :supported_ios_version
# end
# end
# 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) }
# # 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
#
# # Test::Unit
# class PhoneTest < ActiveSupport::TestCase
# should have_db_column(:camera_aperture).
# with_options(precision: 1, null: false)
# end
#
# @return [HaveDbColumnMatcher]
#
def have_db_column(column)
HaveDbColumnMatcher.new(column)
end
class HaveDbColumnMatcher # :nodoc:
# @private
class HaveDbColumnMatcher
def initialize(column)
@column = column
@options = {}

View file

@ -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.
#
# Options:
# * <tt>unique</tt> - whether or not the index has a unique
# constraint. Use <tt>true</tt> to explicitly test for a unique
# constraint. Use <tt>false</tt> to explicitly test for a non-unique
# constraint.
# class CreateBlogs < ActiveRecord::Migration
# def change
# create_table :blogs do |t|
# t.integer :user_id
# end
#
# Examples:
# add_index :blogs, :user_id
# end
# end
#
# it { should have_db_index(:age) }
# it { should have_db_index([:commentable_type, :commentable_id]) }
# it { should have_db_index(:ssn).unique(true) }
# # 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:
#
# # RSpec
# describe Blog do
# it { should have_db_index(:name).unique }
# end
#
# # Test::Unit
# class BlogTest < ActiveSupport::TestCase
# should have_db_index(:name).unique
# end
#
# @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 = {}

View file

@ -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.
#
# it { should have_readonly_attribute(:password) }
# 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
#
# @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

View file

@ -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:
# * <tt>:as</tt> - 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 = {}

View file

@ -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 Shoulda
rescue LoadError
require 'minitest/unit'
ensure
# @private
AssertionError = MiniTest::Assertion
end
else

View file

@ -2,6 +2,7 @@ require 'forwardable'
module Shoulda
module Matchers
# @private
module Doublespeak
class << self
extend Forwardable

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
class Double
attr_reader :calls

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
class DoubleCollection
def initialize(klass)
@klass = klass

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
module DoubleImplementationRegistry
class << self
REGISTRY = {}

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
class ObjectDouble < BasicObject
attr_reader :calls

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
class ProxyImplementation
extend Forwardable

View file

@ -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

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
class StubImplementation
DoubleImplementationRegistry.register(self, :stub)

View file

@ -1,6 +1,7 @@
module Shoulda
module Matchers
module Doublespeak
# @private
class World
def register_double_collection(klass)
double_collection = DoubleCollection.new(klass)

View file

@ -1,5 +1,6 @@
module Shoulda
module Matchers
# @private
class Error < StandardError
def self.create(attributes)
allocate.tap do |error|

View file

@ -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.
#
# Basic Syntax:
# it { should delegate_method(method_name).to(delegate_name) }
# In this example, we test that Courier forwards a call to #deliver onto
# its PostOffice instance:
#
# Options:
# * <tt>:as</tt> - The name of the delegating method. Defaults to
# method_name.
# * <tt>:with_arguments</tt> - Tests that the delegate method is called
# with certain arguments.
# require 'forwardable'
#
# 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) }
# 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
#
# # RSpec
# describe Courier do
# it do
# should delegate_method(:deliver).
# to(:post_office).
# as(:ship).
# with_arguments(expedited: true)
# end
# end
#
# # Test::Unit
# class CourierTest < Test::Unit::TestCase
# should delegate_method(:deliver).
# to(:post_office).
# as(:ship).
# with_arguments(expedited: true)
# end
#
# @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 @@ module Shoulda # :nodoc:
string
end
# @private
class TargetNotDefinedError < StandardError
def message
'Delegation needs a target. Use the #to method to define one, e.g.

View file

@ -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

View file

@ -1,6 +1,8 @@
module Shoulda
module Matchers
# @private
module Integrations
# @private
module NUnitTestCaseDetection
def self.possible_test_case_constants
[
@ -29,6 +31,7 @@ module Shoulda
end
end
# @private
def self.nunit_test_case_constants
Integrations::NUnitTestCaseDetection.test_case_constants
end

View file

@ -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'

View file

@ -1,5 +1,6 @@
module Shoulda
module Matchers
# @private
VERSION = '2.6.1'.freeze
end
end

View file

@ -1,5 +1,6 @@
module Shoulda
module Matchers
# @private
def self.warn(msg)
Kernel.warn "Warning from shoulda-matchers:\n\n#{msg}"
end