mirror of
https://github.com/thoughtbot/shoulda-matchers.git
synced 2022-11-09 12:01:38 -05:00
Move into the Matchers namespace
This commit is contained in:
parent
ecb01a209d
commit
56b0a0439e
94 changed files with 2577 additions and 2518 deletions
4
Rakefile
4
Rakefile
|
@ -7,7 +7,7 @@ require 'rspec/core/rake_task'
|
|||
require 'cucumber/rake/task'
|
||||
|
||||
$LOAD_PATH.unshift("lib")
|
||||
require 'shoulda/version'
|
||||
require 'shoulda/matchers/version'
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
|
@ -31,7 +31,7 @@ RSpec::Core::RakeTask.new(:coverage) do |t|
|
|||
t.pattern = "spec/**/*_spec.rb"
|
||||
end
|
||||
|
||||
eval("$specification = begin; #{IO.read('shoulda.gemspec')}; end")
|
||||
eval("$specification = begin; #{IO.read('shoulda-matchers.gemspec')}; end")
|
||||
Rake::GemPackageTask.new $specification do |pkg|
|
||||
pkg.need_tar = true
|
||||
pkg.need_zip = true
|
||||
|
|
|
@ -33,7 +33,7 @@ Feature: integrate with Rails
|
|||
|
||||
Scenario: generate a rails application and use matchers in Test::Unit
|
||||
When I configure the application to use shoulda-context
|
||||
And I configure the application to use "shoulda" from this project
|
||||
And I configure the application to use "shoulda-matchers" from this project
|
||||
And I write to "test/unit/user_test.rb" with:
|
||||
"""
|
||||
require 'test_helper'
|
||||
|
@ -63,7 +63,7 @@ Feature: integrate with Rails
|
|||
|
||||
Scenario: generate a rails application and use matchers in Rspec
|
||||
When I configure the application to use rspec-rails
|
||||
And I configure the application to use "shoulda" from this project
|
||||
And I configure the application to use "shoulda-matchers" from this project
|
||||
And I run the rspec generator
|
||||
And I write to "spec/models/user_spec.rb" with:
|
||||
"""
|
||||
|
|
8
lib/shoulda-matchers.rb
Normal file
8
lib/shoulda-matchers.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
require 'shoulda/matchers/version'
|
||||
|
||||
if defined?(RSpec)
|
||||
require 'shoulda/matchers/integrations/rspec'
|
||||
else
|
||||
require 'shoulda/matchers/integrations/test_unit'
|
||||
end
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
require 'shoulda/version'
|
||||
|
||||
if defined?(RSpec)
|
||||
require 'shoulda/integrations/rspec'
|
||||
else
|
||||
require 'shoulda/integrations/test_unit'
|
||||
end
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
require 'shoulda/action_controller/assign_to_matcher'
|
||||
require 'shoulda/action_controller/filter_param_matcher'
|
||||
require 'shoulda/action_controller/set_the_flash_matcher'
|
||||
require 'shoulda/action_controller/render_with_layout_matcher'
|
||||
require 'shoulda/action_controller/respond_with_matcher'
|
||||
require 'shoulda/action_controller/respond_with_content_type_matcher'
|
||||
require 'shoulda/action_controller/set_session_matcher'
|
||||
require 'shoulda/action_controller/route_matcher'
|
||||
require 'shoulda/action_controller/redirect_to_matcher'
|
||||
require 'shoulda/action_controller/render_template_matcher'
|
||||
|
||||
module Shoulda
|
||||
# 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 assign_to(:user) }
|
||||
# it { should respond_with(:success) }
|
||||
# it { should render_template(:show) }
|
||||
# it { should not_set_the_flash) }
|
||||
#
|
||||
# it "should do something else really cool" do
|
||||
# assigns[:user].id.should == 1
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the show action
|
||||
module ActionController
|
||||
end
|
||||
end
|
|
@ -1,112 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that the controller assigned to the named instance variable.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_kind_of</tt> - The expected class of the instance variable
|
||||
# being checked.
|
||||
# * <tt>with</tt> - The value that should be assigned.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should assign_to(:user) }
|
||||
# it { should_not assign_to(:user) }
|
||||
# it { should assign_to(:user).with_kind_of(User) }
|
||||
# it { should assign_to(:user).with(@user) }
|
||||
def assign_to(variable)
|
||||
AssignToMatcher.new(variable)
|
||||
end
|
||||
|
||||
class AssignToMatcher # :nodoc:
|
||||
|
||||
def initialize(variable)
|
||||
@variable = variable.to_s
|
||||
@check_value = false
|
||||
end
|
||||
|
||||
def with_kind_of(expected_class)
|
||||
@expected_class = expected_class
|
||||
self
|
||||
end
|
||||
|
||||
def with(expected_value = nil, &block)
|
||||
@check_value = true
|
||||
@expected_value = expected_value
|
||||
@expectation_block = block
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
@expected_value = @context.instance_eval(&@expectation_block) if @expectation_block
|
||||
assigned_value? && kind_of_expected_class? && equal_to_expected_value?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
description = "assign @#{@variable}"
|
||||
description << " with a kind of #{@expected_class}" if @expected_class
|
||||
description
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assigned_value?
|
||||
if !@controller.instance_variables.include?("@#{@variable}")
|
||||
@failure_message =
|
||||
"Expected action to assign a value for @#{@variable}"
|
||||
false
|
||||
else
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign a value for @#{@variable}, " <<
|
||||
"but it was assigned to #{assigned_value.inspect}"
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def kind_of_expected_class?
|
||||
return true unless @expected_class
|
||||
if assigned_value.kind_of?(@expected_class)
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign a kind of #{@expected_class} " <<
|
||||
"for #{@variable}, but got one anyway"
|
||||
true
|
||||
else
|
||||
@failure_message =
|
||||
"Expected action to assign a kind of #{@expected_class} " <<
|
||||
"for #{@variable}, but got #{@variable.inspect} " <<
|
||||
"(#{@variable.class.name})"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def equal_to_expected_value?
|
||||
return true unless @check_value
|
||||
if @expected_value == assigned_value
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign #{@expected_value.inspect} " <<
|
||||
"for #{@variable}, but got it anyway"
|
||||
true
|
||||
else
|
||||
@failure_message =
|
||||
"Expected action to assign #{@expected_value.inspect} " <<
|
||||
"for #{@variable}, but got #{assigned_value.inspect}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_value
|
||||
@controller.instance_variable_get("@#{@variable}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,48 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that filter_parameter_logging is set for the specified key.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should filter_param(:password) }
|
||||
def filter_param(key)
|
||||
FilterParamMatcher.new(key)
|
||||
end
|
||||
|
||||
class FilterParamMatcher # :nodoc:
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
filters_key?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{@key} to be filtered; filtered keys: #{filtered_keys.join(', ')}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{@key} to be filtered"
|
||||
end
|
||||
|
||||
def description
|
||||
"filter #{@key}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filters_key?
|
||||
filtered_keys.include?(@key)
|
||||
end
|
||||
|
||||
def filtered_keys
|
||||
Rails.application.config.filter_parameters.map { |filter| filter.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,60 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller redirected to the given url.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(url_or_description, context, &block)
|
||||
if block
|
||||
@url_block = block
|
||||
@location = @url_or_description
|
||||
else
|
||||
@url = url_or_description
|
||||
@location = @url
|
||||
end
|
||||
@context = context
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
redirects_to_url?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"redirect to #{@location}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirects_to_url?
|
||||
@url = @context.instance_eval(&@url_block) if @url_block
|
||||
begin
|
||||
@context.send(:assert_redirected_to, @url)
|
||||
@negative_failure_message = "Didn't expect to redirect to #{@url}"
|
||||
true
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,52 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller rendered the given template.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should render_template(:show) }
|
||||
def render_template(template)
|
||||
RenderTemplateMatcher.new(template, self)
|
||||
end
|
||||
|
||||
class RenderTemplateMatcher # :nodoc:
|
||||
|
||||
def initialize(template, context)
|
||||
@template = template.to_s
|
||||
@context = context
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
renders_template?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"render template #{@template}"
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def renders_template?
|
||||
begin
|
||||
@context.send(:assert_template, @template)
|
||||
@negative_failure_message = "Didn't expect to render #{@template}"
|
||||
true
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,97 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that the controller rendered with the given layout.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(expected_layout)
|
||||
@expected_layout = expected_layout.to_s unless expected_layout.nil?
|
||||
end
|
||||
|
||||
# Used to provide access to layouts recorded by
|
||||
# ActionController::TemplateAssertions in Rails 3
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
rendered_with_layout? && rendered_with_expected_layout?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "render with "
|
||||
if @expected_layout.nil?
|
||||
description << "a layout"
|
||||
else
|
||||
description << "the #{@expected_layout.inspect} layout"
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rendered_with_layout?
|
||||
!rendered_layouts.empty?
|
||||
end
|
||||
|
||||
def rendered_with_expected_layout?
|
||||
return true if @expected_layout.nil?
|
||||
rendered_layouts.include?(@expected_layout)
|
||||
end
|
||||
|
||||
def rendered_layouts
|
||||
if recorded_layouts
|
||||
recorded_layouts.keys.compact.map { |layout| layout.sub(%r{^layouts/}, '') }
|
||||
else
|
||||
layout = @controller.response.layout
|
||||
if layout.nil?
|
||||
[]
|
||||
else
|
||||
[layout.split('/').last]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def recorded_layouts
|
||||
if @context
|
||||
@context.instance_variable_get('@layouts')
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"to #{description}"
|
||||
end
|
||||
|
||||
def result
|
||||
if rendered_with_layout?
|
||||
"rendered with " <<
|
||||
rendered_layouts.map { |layout| layout.inspect }.join(", ")
|
||||
else
|
||||
"rendered without a layout"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,72 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller responded with expected 'response' content type.
|
||||
#
|
||||
# You can pass an explicit content type such as 'application/rss+xml'
|
||||
# or its symbolic equivalent :rss
|
||||
# or a regular expression such as /rss/
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should respond_with_content_type(:xml) }
|
||||
# it { should respond_with_content_type(:csv) }
|
||||
# it { should respond_with_content_type(:atom) }
|
||||
# it { should respond_with_content_type(:yaml) }
|
||||
# it { should respond_with_content_type(:text) }
|
||||
# it { should respond_with_content_type('application/rss+xml') }
|
||||
# it { should respond_with_content_type(/json/) }
|
||||
def respond_with_content_type(content_type)
|
||||
RespondWithContentTypeMatcher.new(content_type)
|
||||
end
|
||||
|
||||
class RespondWithContentTypeMatcher # :nodoc:
|
||||
|
||||
def initialize(content_type)
|
||||
@content_type = if content_type.is_a?(Symbol)
|
||||
lookup_by_extension(content_type)
|
||||
else
|
||||
content_type
|
||||
end
|
||||
end
|
||||
|
||||
def description
|
||||
"respond with content type of #{@content_type}"
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
if @content_type.is_a?(Regexp)
|
||||
response_content_type =~ @content_type
|
||||
else
|
||||
response_content_type == @content_type
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def response_content_type
|
||||
@controller.response.content_type.to_s
|
||||
end
|
||||
|
||||
def lookup_by_extension(extension)
|
||||
Mime::Type.lookup_by_extension(extension.to_s).to_s
|
||||
end
|
||||
|
||||
def expectation
|
||||
"content type to be #{@content_type}, " <<
|
||||
"but was #{response_content_type}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,83 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller responded with expected 'response' 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.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(status)
|
||||
@status = symbol_to_status_code(status)
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
correct_status_code? || correct_status_code_range?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"respond with #{@status}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def correct_status_code?
|
||||
response_code == @status
|
||||
end
|
||||
|
||||
def correct_status_code_range?
|
||||
@status.is_a?(Range) &&
|
||||
@status.include?(response_code)
|
||||
end
|
||||
|
||||
def response_code
|
||||
@controller.response.response_code
|
||||
end
|
||||
|
||||
def symbol_to_status_code(potential_symbol)
|
||||
case potential_symbol
|
||||
when :success then 200
|
||||
when :redirect then 300..399
|
||||
when :missing then 404
|
||||
when :error then 500..599
|
||||
when Symbol
|
||||
if defined?(::Rack::Utils::SYMBOL_TO_STATUS_CODE)
|
||||
::Rack::Utils::SYMBOL_TO_STATUS_CODE[potential_symbol]
|
||||
else
|
||||
::ActionController::Base::SYMBOL_TO_STATUS_CODE[potential_symbol]
|
||||
end
|
||||
else
|
||||
potential_symbol
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"response to be a #{@status}, but was #{response_code}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,91 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that requesting +path+ using +method+ routes to +options+.
|
||||
#
|
||||
# If you don't specify a controller, it will use the controller from the
|
||||
# example group.
|
||||
#
|
||||
# +to_param+ is called on the +options+ given.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# it { should route(:get, "/posts").
|
||||
# to(:controller => :posts, :action => :index) }
|
||||
# it { should route(:get, "/posts/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(:edit, "/posts/1").to(:action => :show, :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) }
|
||||
def route(method, path)
|
||||
RouteMatcher.new(method, path, self)
|
||||
end
|
||||
|
||||
class RouteMatcher # :nodoc:
|
||||
|
||||
def initialize(method, path, context)
|
||||
@method = method
|
||||
@path = path
|
||||
@context = context
|
||||
end
|
||||
|
||||
def to(params)
|
||||
@params = params
|
||||
stringify_params!
|
||||
self
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
guess_controller!
|
||||
route_recognized?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"route #{@method.to_s.upcase} #{@path} to/from #{@params.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def guess_controller!
|
||||
@params[:controller] ||= @controller.controller_path
|
||||
end
|
||||
|
||||
def stringify_params!
|
||||
@params.each do |key, value|
|
||||
@params[key] = value.is_a?(Array) ? value.collect {|v| v.to_param } : value.to_param
|
||||
end
|
||||
end
|
||||
|
||||
def route_recognized?
|
||||
begin
|
||||
@context.send(:assert_routing,
|
||||
{ :method => @method, :path => @path },
|
||||
@params)
|
||||
|
||||
@negative_failure_message = "Didn't expect to #{description}"
|
||||
true
|
||||
rescue ::ActionController::RoutingError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,96 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that a session key was set to the expected value.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
end
|
||||
|
||||
def to(value = nil, &block)
|
||||
@value = value
|
||||
@value_block = block
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
@value = @context.instance_eval(&@value_block) if @value_block
|
||||
(assigned_value? && assigned_correct_value?) || cleared_value?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Didn't expect #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "set session variable #{@key.inspect}"
|
||||
description << " to #{@value.inspect}" if defined?(@value)
|
||||
description
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assigned_value?
|
||||
!assigned_value.nil?
|
||||
end
|
||||
|
||||
def cleared_value?
|
||||
defined?(@value) && @value.nil? && assigned_value.nil?
|
||||
end
|
||||
|
||||
def assigned_correct_value?
|
||||
return true if @value.nil?
|
||||
assigned_value == @value
|
||||
end
|
||||
|
||||
def assigned_value
|
||||
session[@key]
|
||||
end
|
||||
|
||||
def session
|
||||
if @controller.request.respond_to?(:session)
|
||||
@controller.request.session.to_hash
|
||||
else
|
||||
@controller.response.session.data
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
expectation = "session variable #{@key} to be set"
|
||||
expectation << " to #{@value.inspect}" if @value
|
||||
expectation
|
||||
end
|
||||
|
||||
def result
|
||||
if session.empty?
|
||||
"no session variables were set"
|
||||
else
|
||||
"the session was #{session.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,92 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
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).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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.to(/logged in/i).now }
|
||||
# it { should_not set_the_flash }
|
||||
def set_the_flash
|
||||
SetTheFlashMatcher.new
|
||||
end
|
||||
|
||||
class SetTheFlashMatcher # :nodoc:
|
||||
|
||||
def to(value)
|
||||
@value = value
|
||||
self
|
||||
end
|
||||
|
||||
def now
|
||||
@now = true
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
sets_the_flash? && string_value_matches? && regexp_value_matches?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
description = "set the flash"
|
||||
description << " to #{@value.inspect}" unless @value.nil?
|
||||
description
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sets_the_flash?
|
||||
!flash.blank?
|
||||
end
|
||||
|
||||
def string_value_matches?
|
||||
return true unless String === @value
|
||||
flash.values.any? {|value| value == @value }
|
||||
end
|
||||
|
||||
def regexp_value_matches?
|
||||
return true unless Regexp === @value
|
||||
flash.values.any? {|value| value =~ @value }
|
||||
end
|
||||
|
||||
def flash
|
||||
return @flash if @flash
|
||||
@flash = @controller.flash.dup
|
||||
@flash.sweep unless @now
|
||||
@flash
|
||||
end
|
||||
|
||||
def expectation
|
||||
expectation = "the flash#{".now" if @now} to be set"
|
||||
expectation << " to #{@value.inspect}" unless @value.nil?
|
||||
expectation << ", but #{flash_description}"
|
||||
expectation
|
||||
end
|
||||
|
||||
def flash_description
|
||||
if flash.blank?
|
||||
"no flash was set"
|
||||
else
|
||||
"was #{flash.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
require 'shoulda/action_mailer/have_sent_email'
|
||||
|
||||
module Shoulda
|
||||
# = Matchers for your mailers
|
||||
#
|
||||
# This matcher will test that email is sent properly
|
||||
#
|
||||
# describe User do
|
||||
# it { should have_sent_email.with_subject(/is spam$/) }
|
||||
# it { should have_sent_email.from('do-not-reply@example.com') }
|
||||
# it { should have_sent_email.with_body(/is spam\./) }
|
||||
# it { should have_sent_email.to('myself@me.com') }
|
||||
# it { should have_sent_email.with_subject(/spam/).
|
||||
# from('do-not-reply@example.com').
|
||||
# with_body(/spam/).
|
||||
# to('myself@me.com') }
|
||||
# end
|
||||
module ActionMailer
|
||||
end
|
||||
end
|
|
@ -1,108 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActionMailer # :nodoc:
|
||||
|
||||
# The right email is sent.
|
||||
#
|
||||
# it { should have_sent_email.with_subject(/is spam$/) }
|
||||
# it { should have_sent_email.from('do-not-reply@example.com') }
|
||||
# it { should have_sent_email.with_body(/is spam\./) }
|
||||
# it { should have_sent_email.to('myself@me.com') }
|
||||
# it { should have_sent_email.with_subject(/spam/).
|
||||
# from('do-not-reply@example.com').
|
||||
# with_body(/spam/).
|
||||
# to('myself@me.com') }
|
||||
def have_sent_email
|
||||
HaveSentEmailMatcher.new
|
||||
end
|
||||
|
||||
class HaveSentEmailMatcher # :nodoc:
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def with_subject(email_subject)
|
||||
@email_subject = email_subject
|
||||
self
|
||||
end
|
||||
|
||||
def from(sender)
|
||||
@sender = sender
|
||||
self
|
||||
end
|
||||
|
||||
def with_body(body)
|
||||
@body = body
|
||||
self
|
||||
end
|
||||
|
||||
def to(recipient)
|
||||
@recipient = recipient
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
::ActionMailer::Base.deliveries.each do |mail|
|
||||
@subject_failed = !regexp_or_string_match(mail.subject, @email_subject) if @email_subject
|
||||
@body_failed = !regexp_or_string_match(mail.body, @body) if @body
|
||||
@sender_failed = !regexp_or_string_match_in_array(mail.from, @sender) if @sender
|
||||
@recipient_failed = !regexp_or_string_match_in_array(mail.to, @recipient) if @recipient
|
||||
return true unless anything_failed?
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"send an email"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expectation
|
||||
expectation = "sent email"
|
||||
expectation << " with subject #{@email_subject.inspect}" if @subject_failed
|
||||
expectation << " with body #{@body.inspect}" if @body_failed
|
||||
expectation << " from #{@sender.inspect}" if @sender_failed
|
||||
expectation << " to #{@recipient.inspect}" if @recipient_failed
|
||||
expectation << "\nDeliveries:\n#{inspect_deliveries}"
|
||||
end
|
||||
|
||||
def inspect_deliveries
|
||||
::ActionMailer::Base.deliveries.map do |delivery|
|
||||
"#{delivery.subject.inspect} to #{delivery.to.inspect}"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def anything_failed?
|
||||
@subject_failed || @body_failed || @sender_failed || @recipient_failed
|
||||
end
|
||||
|
||||
def regexp_or_string_match(a_string, a_regexp_or_string)
|
||||
case a_regexp_or_string
|
||||
when Regexp
|
||||
a_string =~ a_regexp_or_string
|
||||
when String
|
||||
a_string == a_regexp_or_string
|
||||
end
|
||||
end
|
||||
|
||||
def regexp_or_string_match_in_array(an_array, a_regexp_or_string)
|
||||
case a_regexp_or_string
|
||||
when Regexp
|
||||
an_array.any? { |string| string =~ a_regexp_or_string }
|
||||
when String
|
||||
an_array.include?(a_regexp_or_string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
require 'shoulda/active_record/helpers'
|
||||
require 'shoulda/active_record/validation_matcher'
|
||||
require 'shoulda/active_record/allow_value_matcher'
|
||||
require 'shoulda/active_record/ensure_length_of_matcher'
|
||||
require 'shoulda/active_record/ensure_inclusion_of_matcher'
|
||||
require 'shoulda/active_record/validate_presence_of_matcher'
|
||||
require 'shoulda/active_record/validate_format_of_matcher'
|
||||
require 'shoulda/active_record/validate_uniqueness_of_matcher'
|
||||
require 'shoulda/active_record/validate_acceptance_of_matcher'
|
||||
require 'shoulda/active_record/validate_numericality_of_matcher'
|
||||
require 'shoulda/active_record/association_matcher'
|
||||
require 'shoulda/active_record/have_db_column_matcher'
|
||||
require 'shoulda/active_record/have_db_index_matcher'
|
||||
require 'shoulda/active_record/have_readonly_attribute_matcher'
|
||||
require 'shoulda/active_record/allow_mass_assignment_of_matcher'
|
||||
|
||||
|
||||
module Shoulda
|
||||
# = Matchers for your active record models
|
||||
#
|
||||
# These matchers will test most of the validations and associations for your
|
||||
# ActiveRecord models.
|
||||
#
|
||||
# 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 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
|
|
@ -1,81 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the attribute can be set on mass update.
|
||||
#
|
||||
# it { should_not allow_mass_assignment_of(:password) }
|
||||
# it { should allow_mass_assignment_of(:first_name) }
|
||||
#
|
||||
def allow_mass_assignment_of(value)
|
||||
AllowMassAssignmentOfMatcher.new(value)
|
||||
end
|
||||
|
||||
class AllowMassAssignmentOfMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute.to_s
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
if attr_mass_assignable?
|
||||
if whitelisting?
|
||||
@negative_failure_message = "#{@attribute} was made accessible"
|
||||
else
|
||||
if protected_attributes.empty?
|
||||
@negative_failure_message = "no attributes were protected"
|
||||
else
|
||||
@negative_failure_message = "#{class_name} is protecting " <<
|
||||
"#{protected_attributes.to_a.to_sentence}, " <<
|
||||
"but not #{@attribute}."
|
||||
end
|
||||
end
|
||||
true
|
||||
else
|
||||
if whitelisting?
|
||||
@failure_message =
|
||||
"Expected #{@attribute} to be accessible"
|
||||
else
|
||||
@failure_message =
|
||||
"Did not expect #{@attribute} to be protected"
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"allow mass assignment of #{@attribute}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def protected_attributes
|
||||
@protected_attributes ||= (@subject.class.protected_attributes || [])
|
||||
end
|
||||
|
||||
def accessible_attributes
|
||||
@accessible_attributes ||= (@subject.class.accessible_attributes || [])
|
||||
end
|
||||
|
||||
def whitelisting?
|
||||
!accessible_attributes.empty?
|
||||
end
|
||||
|
||||
def attr_mass_assignable?
|
||||
if whitelisting?
|
||||
accessible_attributes.include?(@attribute)
|
||||
else
|
||||
!protected_attributes.include?(@attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,108 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the attribute can be set to the given value.
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# Example:
|
||||
# it { should_not allow_value('bad').for(:isbn) }
|
||||
# it { should allow_value("isbn 1 2345 6789 0").for(:isbn) }
|
||||
#
|
||||
def allow_value(value)
|
||||
AllowValueMatcher.new(value)
|
||||
end
|
||||
|
||||
class AllowValueMatcher # :nodoc:
|
||||
include Helpers
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
@attribute = attribute
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(instance)
|
||||
@instance = instance
|
||||
if Symbol === @expected_message
|
||||
@expected_message = default_error_message(@expected_message)
|
||||
end
|
||||
@instance.send("#{@attribute}=", @value)
|
||||
!errors_match?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Did not expect #{expectation}, got error: #{@matched_error}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Expected #{expectation}, got #{error_description}"
|
||||
end
|
||||
|
||||
def description
|
||||
"allow #{@attribute} to be set to #{@value.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def errors_match?
|
||||
@instance.valid?
|
||||
@errors = errors_for_attribute(@instance, @attribute)
|
||||
@errors = [@errors] unless @errors.is_a?(Array)
|
||||
@expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?)
|
||||
end
|
||||
|
||||
def errors_for_attribute(instance, attribute)
|
||||
if instance.errors.respond_to?(:[])
|
||||
instance.errors[attribute]
|
||||
else
|
||||
instance.errors.on(attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def errors_match_regexp?
|
||||
if Regexp === @expected_message
|
||||
@matched_error = @errors.detect { |e| e =~ @expected_message }
|
||||
!@matched_error.nil?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def errors_match_string?
|
||||
if @errors.include?(@expected_message)
|
||||
@matched_error = @expected_message
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"errors " <<
|
||||
(@expected_message ? "to include #{@expected_message.inspect} " : "") <<
|
||||
"when #{@attribute} is set to #{@value.inspect}"
|
||||
end
|
||||
|
||||
def error_description
|
||||
if @instance.errors.empty?
|
||||
"no errors"
|
||||
else
|
||||
"errors: #{pretty_error_messages(@instance)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,224 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensure that the belongs_to relationship exists.
|
||||
#
|
||||
# it { should belong_to(:parent) }
|
||||
#
|
||||
def belong_to(name)
|
||||
AssociationMatcher.new(:belongs_to, name)
|
||||
end
|
||||
|
||||
# Ensures that the has_many relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>through</tt> - association name for <tt>has_many :through</tt>
|
||||
# * <tt>dependent</tt> - tests that the association makes use of the
|
||||
# dependent option.
|
||||
#
|
||||
# Example:
|
||||
# it { should have_many(:friends) }
|
||||
# it { should have_many(:enemies).through(:friends) }
|
||||
# it { should have_many(:enemies).dependent(:destroy) }
|
||||
#
|
||||
def have_many(name)
|
||||
AssociationMatcher.new(:has_many, name)
|
||||
end
|
||||
|
||||
# Ensure that the has_one relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:dependent</tt> - tests that the association makes use of the
|
||||
# dependent option.
|
||||
#
|
||||
# Example:
|
||||
# it { should have_one(:god) } # unless hindu
|
||||
#
|
||||
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.
|
||||
#
|
||||
# it { should have_and_belong_to_many(:posts) }
|
||||
#
|
||||
def have_and_belong_to_many(name)
|
||||
AssociationMatcher.new(:has_and_belongs_to_many, name)
|
||||
end
|
||||
|
||||
class AssociationMatcher # :nodoc:
|
||||
def initialize(macro, name)
|
||||
@macro = macro
|
||||
@name = name
|
||||
end
|
||||
|
||||
def through(through)
|
||||
@through = through
|
||||
self
|
||||
end
|
||||
|
||||
def dependent(dependent)
|
||||
@dependent = dependent
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
association_exists? &&
|
||||
macro_correct? &&
|
||||
foreign_key_exists? &&
|
||||
through_association_valid? &&
|
||||
dependent_correct? &&
|
||||
join_table_exists?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "#{macro_description} #{@name}"
|
||||
description += " through #{@through}" if @through
|
||||
description += " dependent => #{@dependent}" if @dependent
|
||||
description
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def association_exists?
|
||||
if reflection.nil?
|
||||
@missing = "no association called #{@name}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def macro_correct?
|
||||
if reflection.macro == @macro
|
||||
true
|
||||
else
|
||||
@missing = "actual association type was #{reflection.macro}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def foreign_key_exists?
|
||||
!(belongs_foreign_key_missing? || has_foreign_key_missing?)
|
||||
end
|
||||
|
||||
def belongs_foreign_key_missing?
|
||||
@macro == :belongs_to && !class_has_foreign_key?(model_class)
|
||||
end
|
||||
|
||||
def has_foreign_key_missing?
|
||||
[:has_many, :has_one].include?(@macro) &&
|
||||
!through? &&
|
||||
!class_has_foreign_key?(associated_class)
|
||||
end
|
||||
|
||||
def through_association_valid?
|
||||
@through.nil? || (through_association_exists? && through_association_correct?)
|
||||
end
|
||||
|
||||
def through_association_exists?
|
||||
if through_reflection.nil?
|
||||
@missing = "#{model_class.name} does not have any relationship to #{@through}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def through_association_correct?
|
||||
if @through == reflection.options[:through]
|
||||
true
|
||||
else
|
||||
@missing = "Expected #{model_class.name} to have #{@name} through #{@through}, " <<
|
||||
"but got it through #{reflection.options[:through]}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def dependent_correct?
|
||||
if @dependent.nil? || @dependent.to_s == reflection.options[:dependent].to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{@name} should have #{@dependent} dependency"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def join_table_exists?
|
||||
if @macro != :has_and_belongs_to_many ||
|
||||
::ActiveRecord::Base.connection.tables.include?(join_table.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "join table #{join_table} doesn't exist"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def class_has_foreign_key?(klass)
|
||||
if klass.column_names.include?(foreign_key.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "#{klass} does not have a #{foreign_key} foreign key."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def join_table
|
||||
reflection.options[:join_table]
|
||||
end
|
||||
|
||||
def associated_class
|
||||
reflection.klass
|
||||
end
|
||||
|
||||
def foreign_key
|
||||
reflection.primary_key_name
|
||||
end
|
||||
|
||||
def through?
|
||||
reflection.options[:through]
|
||||
end
|
||||
|
||||
def reflection
|
||||
@reflection ||= model_class.reflect_on_association(@name)
|
||||
end
|
||||
|
||||
def through_reflection
|
||||
@through_reflection ||= model_class.reflect_on_association(@through)
|
||||
end
|
||||
|
||||
def expectation
|
||||
"#{model_class.name} to have a #{@macro} association called #{@name}"
|
||||
end
|
||||
|
||||
def macro_description
|
||||
case @macro.to_s
|
||||
when 'belongs_to' then 'belong to'
|
||||
when 'has_many' then 'have many'
|
||||
when 'has_one' then 'have one'
|
||||
when 'has_and_belongs_to_many' then
|
||||
'have and belong to many'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,85 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensure that the attribute's value is in the range specified
|
||||
#
|
||||
# Options:
|
||||
# * <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.
|
||||
#
|
||||
# Example:
|
||||
# it { should ensure_inclusion_of(:age).in_range(0..100) }
|
||||
#
|
||||
def ensure_inclusion_of(attr)
|
||||
EnsureInclusionOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def in_range(range)
|
||||
@range = range
|
||||
@minimum = range.first
|
||||
@maximum = range.last
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
if message
|
||||
@low_message = message
|
||||
@high_message = message
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def with_low_message(message)
|
||||
@low_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def with_high_message(message)
|
||||
@high_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
"ensure inclusion of #{@attribute} in #{@range.inspect}"
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
|
||||
@low_message ||= :inclusion
|
||||
@high_message ||= :inclusion
|
||||
|
||||
disallows_lower_value &&
|
||||
allows_minimum_value &&
|
||||
disallows_higher_value &&
|
||||
allows_maximum_value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disallows_lower_value
|
||||
@minimum == 0 || disallows_value_of(@minimum - 1, @low_message)
|
||||
end
|
||||
|
||||
def disallows_higher_value
|
||||
disallows_value_of(@maximum + 1, @high_message)
|
||||
end
|
||||
|
||||
def allows_minimum_value
|
||||
allows_value_of(@minimum, @low_message)
|
||||
end
|
||||
|
||||
def allows_maximum_value
|
||||
allows_value_of(@maximum, @high_message)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,139 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the length of the attribute is validated.
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# 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:
|
||||
include Helpers
|
||||
|
||||
def is_at_least(length)
|
||||
@minimum = length
|
||||
@short_message ||= :too_short
|
||||
self
|
||||
end
|
||||
|
||||
def is_at_most(length)
|
||||
@maximum = length
|
||||
@long_message ||= :too_long
|
||||
self
|
||||
end
|
||||
|
||||
def is_equal_to(length)
|
||||
@minimum = length
|
||||
@maximum = length
|
||||
@short_message ||= :wrong_length
|
||||
self
|
||||
end
|
||||
|
||||
def with_short_message(message)
|
||||
@short_message = message if message
|
||||
self
|
||||
end
|
||||
alias_method :with_message, :with_short_message
|
||||
|
||||
def with_long_message(message)
|
||||
@long_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
description = "ensure #{@attribute} has a length "
|
||||
if @minimum && @maximum
|
||||
if @minimum == @maximum
|
||||
description << "of exactly #{@minimum}"
|
||||
else
|
||||
description << "between #{@minimum} and #{@maximum}"
|
||||
end
|
||||
else
|
||||
description << "of at least #{@minimum}" if @minimum
|
||||
description << "of at most #{@maximum}" if @maximum
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
translate_messages!
|
||||
disallows_lower_length &&
|
||||
allows_minimum_length &&
|
||||
((@minimum == @maximum) ||
|
||||
(disallows_higher_length &&
|
||||
allows_maximum_length))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def translate_messages!
|
||||
if Symbol === @short_message
|
||||
@short_message = default_error_message(@short_message,
|
||||
:count => @minimum)
|
||||
end
|
||||
|
||||
if Symbol === @long_message
|
||||
@long_message = default_error_message(@long_message,
|
||||
:count => @maximum)
|
||||
end
|
||||
end
|
||||
|
||||
def disallows_lower_length
|
||||
@minimum == 0 ||
|
||||
@minimum.nil? ||
|
||||
disallows_length_of(@minimum - 1, @short_message)
|
||||
end
|
||||
|
||||
def disallows_higher_length
|
||||
@maximum.nil? || disallows_length_of(@maximum + 1, @long_message)
|
||||
end
|
||||
|
||||
def allows_minimum_length
|
||||
allows_length_of(@minimum, @short_message)
|
||||
end
|
||||
|
||||
def allows_maximum_length
|
||||
allows_length_of(@maximum, @long_message)
|
||||
end
|
||||
|
||||
def allows_length_of(length, message)
|
||||
length.nil? || allows_value_of(string_of_length(length), message)
|
||||
end
|
||||
|
||||
def disallows_length_of(length, message)
|
||||
length.nil? || disallows_value_of(string_of_length(length), message)
|
||||
end
|
||||
|
||||
def string_of_length(length)
|
||||
'x' * length
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,167 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures the database column exists.
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
# 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) }
|
||||
#
|
||||
def have_db_column(column)
|
||||
HaveDbColumnMatcher.new(:have_db_column, column)
|
||||
end
|
||||
|
||||
class HaveDbColumnMatcher # :nodoc:
|
||||
def initialize(macro, column)
|
||||
@macro = macro
|
||||
@column = column
|
||||
end
|
||||
|
||||
def of_type(column_type)
|
||||
@column_type = column_type
|
||||
self
|
||||
end
|
||||
|
||||
def with_options(opts = {})
|
||||
@precision = opts[:precision]
|
||||
@limit = opts[:limit]
|
||||
@default = opts[:default]
|
||||
@null = opts[:null]
|
||||
@scale = opts[:scale]
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
column_exists? &&
|
||||
correct_column_type? &&
|
||||
correct_precision? &&
|
||||
correct_limit? &&
|
||||
correct_default? &&
|
||||
correct_null? &&
|
||||
correct_scale?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
desc = "have db column named #{@column}"
|
||||
desc << " of type #{@column_type}" unless @column_type.nil?
|
||||
desc << " of precision #{@precision}" unless @precision.nil?
|
||||
desc << " of limit #{@limit}" unless @limit.nil?
|
||||
desc << " of default #{@default}" unless @default.nil?
|
||||
desc << " of null #{@null}" unless @null.nil?
|
||||
desc << " of primary #{@primary}" unless @primary.nil?
|
||||
desc << " of scale #{@scale}" unless @scale.nil?
|
||||
desc
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def column_exists?
|
||||
if model_class.column_names.include?(@column.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} does not have a db column named #{@column}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_column_type?
|
||||
return true if @column_type.nil?
|
||||
if matched_column.type.to_s == @column_type.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of type #{matched_column.type}, not #{@column_type}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_precision?
|
||||
return true if @precision.nil?
|
||||
if matched_column.precision.to_s == @precision.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of precision #{matched_column.precision}, " <<
|
||||
"not #{@precision}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_limit?
|
||||
return true if @limit.nil?
|
||||
if matched_column.limit.to_s == @limit.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of limit #{matched_column.limit}, " <<
|
||||
"not #{@limit}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_default?
|
||||
return true if @default.nil?
|
||||
if matched_column.default.to_s == @default.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of default #{matched_column.default}, " <<
|
||||
"not #{@default}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_null?
|
||||
return true if @null.nil?
|
||||
if matched_column.null.to_s == @null.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of null #{matched_column.null}, " <<
|
||||
"not #{@null}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_scale?
|
||||
return true if @scale.nil?
|
||||
if matched_column.scale.to_s == @scale.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of scale #{matched_column.scale}, not #{@scale}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def matched_column
|
||||
model_class.columns.detect { |each| each.name == @column.to_s }
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def expectation
|
||||
expected = "#{model_class.name} to #{description}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,110 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that there are DB indices on the given columns or tuples of
|
||||
# columns.
|
||||
#
|
||||
# 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. Use <tt>nil</tt> if you don't care whether the index is
|
||||
# unique or not. Default = <tt>nil</tt>
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# it { should have_db_index(:age) }
|
||||
# it { should have_db_index([:commentable_type, :commentable_id]) }
|
||||
# it { should have_db_index(:ssn).unique(true) }
|
||||
#
|
||||
def have_db_index(columns)
|
||||
HaveDbIndexMatcher.new(:have_index, columns)
|
||||
end
|
||||
|
||||
class HaveDbIndexMatcher # :nodoc:
|
||||
def initialize(macro, columns)
|
||||
@macro = macro
|
||||
@columns = normalize_columns_to_array(columns)
|
||||
end
|
||||
|
||||
def unique(unique)
|
||||
@unique = unique
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
index_exists? && correct_unique?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"have a #{index_type} index on columns #{@columns.join(' and ')}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def index_exists?
|
||||
! matched_index.nil?
|
||||
end
|
||||
|
||||
def correct_unique?
|
||||
return true if @unique.nil?
|
||||
if matched_index.unique == @unique
|
||||
true
|
||||
else
|
||||
@missing = "#{table_name} has an index named #{matched_index.name} " <<
|
||||
"of unique #{matched_index.unique}, not #{@unique}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def matched_index
|
||||
indexes.detect { |each| each.columns == @columns }
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def table_name
|
||||
model_class.table_name
|
||||
end
|
||||
|
||||
def indexes
|
||||
::ActiveRecord::Base.connection.indexes(table_name)
|
||||
end
|
||||
|
||||
def expectation
|
||||
expected = "#{model_class.name} to #{description}"
|
||||
end
|
||||
|
||||
def index_type
|
||||
case @unique
|
||||
when nil
|
||||
''
|
||||
when false
|
||||
'non-unique'
|
||||
else
|
||||
'unique'
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_columns_to_array(columns)
|
||||
if columns.class == Array
|
||||
columns.collect { |each| each.to_s }
|
||||
else
|
||||
[columns.to_s]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,57 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the attribute cannot be changed once the record has been
|
||||
# created.
|
||||
#
|
||||
# it { should have_readonly_attributes(:password) }
|
||||
#
|
||||
def have_readonly_attribute(value)
|
||||
HaveReadonlyAttributeMatcher.new(value)
|
||||
end
|
||||
|
||||
class HaveReadonlyAttributeMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute.to_s
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
if readonly_attributes.include?(@attribute)
|
||||
@negative_failure_message =
|
||||
"Did not expect #{@attribute} to be read-only"
|
||||
true
|
||||
else
|
||||
if readonly_attributes.empty?
|
||||
@failure_message = "#{class_name} attribute #{@attribute} " <<
|
||||
"is not read-only"
|
||||
else
|
||||
@failure_message = "#{class_name} is making " <<
|
||||
"#{readonly_attributes.to_sentence} " <<
|
||||
"read-only, but not #{@attribute}."
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"make #{@attribute} read-only"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def readonly_attributes
|
||||
@readonly_attributes ||= (@subject.class.readonly_attributes || [])
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
module Helpers
|
||||
def pretty_error_messages(obj) # :nodoc:
|
||||
obj.errors.map do |a, m|
|
||||
msg = "#{a} #{m}"
|
||||
msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
|
||||
end
|
||||
end
|
||||
|
||||
# Helper method that determines the default error message used by Active
|
||||
# Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
|
||||
# introduced I18n module used for localization.
|
||||
#
|
||||
# default_error_message(:blank)
|
||||
# default_error_message(:too_short, :count => 5)
|
||||
# default_error_message(:too_long, :count => 60)
|
||||
def default_error_message(key, values = {})
|
||||
if Object.const_defined?(:I18n) # Rails >= 2.2
|
||||
result = I18n.translate("activerecord.errors.messages.#{key}", values)
|
||||
if result =~ /^translation missing/
|
||||
I18n.translate("errors.messages.#{key}", values)
|
||||
else
|
||||
result
|
||||
end
|
||||
else # Rails <= 2.1.x
|
||||
::ActiveRecord::Errors.default_error_messages[key] % values[:count]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model cannot be saved the given attribute is not
|
||||
# accepted.
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# Example:
|
||||
# it { should validate_acceptance_of(:eula) }
|
||||
#
|
||||
def validate_acceptance_of(attr)
|
||||
ValidateAcceptanceOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :accepted
|
||||
disallows_value_of(false, @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"require #{@attribute} to be accepted"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,63 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model is not valid if the given attribute is not
|
||||
# formatted correctly.
|
||||
#
|
||||
# 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>.
|
||||
# * <tt>with(string to test against)</tt>
|
||||
# * <tt>not_with(string to test against)</tt>
|
||||
#
|
||||
# Examples:
|
||||
# it { should validate_format_of(:name).
|
||||
# with('12345').
|
||||
# with_message(/is not optional/) }
|
||||
# it { should validate_format_of(:name).
|
||||
# not_with('12D45').
|
||||
# with_message(/is not optional/) }
|
||||
#
|
||||
def validate_format_of(attr)
|
||||
ValidateFormatOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateFormatOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
super
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def with(value)
|
||||
raise "You may not call both with and not_with" if @value_to_fail
|
||||
@value_to_pass = value
|
||||
self
|
||||
end
|
||||
|
||||
def not_with(value)
|
||||
raise "You may not call both with and not_with" if @value_to_pass
|
||||
@value_to_fail = value
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :blank
|
||||
return disallows_value_of(@value_to_fail, @expected_message) if @value_to_fail
|
||||
allows_value_of(@value_to_pass, @expected_message) if @value_to_pass
|
||||
end
|
||||
|
||||
def description
|
||||
"#{@attribute} have a valid format"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensure that the attribute is numeric
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# Example:
|
||||
# it { should validate_numericality_of(:age) }
|
||||
#
|
||||
def validate_numericality_of(attr)
|
||||
ValidateNumericalityOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :not_a_number
|
||||
disallows_value_of('abcd', @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"only allow numeric values for #{@attribute}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,58 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model is not valid if the given attribute is not
|
||||
# present.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
|
||||
# Defaults to the translation for <tt>:blank</tt>.
|
||||
#
|
||||
# Examples:
|
||||
# it { should validate_presence_of(:name) }
|
||||
# it { should validate_presence_of(:name).
|
||||
# with_message(/is not optional/) }
|
||||
#
|
||||
def validate_presence_of(attr)
|
||||
ValidatePresenceOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :blank
|
||||
disallows_value_of(blank_value, @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"require #{@attribute} to be set"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blank_value
|
||||
if collection?
|
||||
[]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def collection?
|
||||
if reflection = @subject.class.reflect_on_association(@attribute)
|
||||
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,146 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model is invalid if the given attribute is not unique.
|
||||
#
|
||||
# Internally, this uses values from existing records to test validations,
|
||||
# so this will always fail if you have not saved at least one record for
|
||||
# the model being tested, like so:
|
||||
#
|
||||
# describe User do
|
||||
# before(:each) { User.create!(:email => 'address@example.com') }
|
||||
# it { should validate_uniqueness_of(:email) }
|
||||
# end
|
||||
#
|
||||
# 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>: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.
|
||||
#
|
||||
# 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 }
|
||||
#
|
||||
def validate_uniqueness_of(attr)
|
||||
ValidateUniquenessOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc:
|
||||
include Helpers
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute
|
||||
end
|
||||
|
||||
def scoped_to(*scopes)
|
||||
@scopes = [*scopes].flatten
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message
|
||||
self
|
||||
end
|
||||
|
||||
def case_insensitive
|
||||
@case_insensitive = true
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
result = "require "
|
||||
result << "case sensitive " unless @case_insensitive
|
||||
result << "unique value for #{@attribute}"
|
||||
result << " scoped to #{@scopes.join(', ')}" unless @scopes.blank?
|
||||
result
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject.class.new
|
||||
@expected_message ||= :taken
|
||||
find_existing &&
|
||||
set_scoped_attributes &&
|
||||
validate_attribute &&
|
||||
validate_after_scope_change
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_existing
|
||||
if @existing = @subject.class.find(:first)
|
||||
true
|
||||
else
|
||||
@failure_message = "Can't find first #{class_name}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def set_scoped_attributes
|
||||
unless @scopes.blank?
|
||||
@scopes.each do |scope|
|
||||
setter = :"#{scope}="
|
||||
unless @subject.respond_to?(setter)
|
||||
@failure_message =
|
||||
"#{class_name} doesn't seem to have a #{scope} attribute."
|
||||
return false
|
||||
end
|
||||
@subject.send("#{scope}=", @existing.send(scope))
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def validate_attribute
|
||||
disallows_value_of(existing_value, @expected_message)
|
||||
end
|
||||
|
||||
# TODO: There is a chance that we could change the scoped field
|
||||
# to a value that's already taken. An alternative implementation
|
||||
# could actually find all values for scope and create a unique
|
||||
def validate_after_scope_change
|
||||
if @scopes.blank?
|
||||
true
|
||||
else
|
||||
@scopes.all? do |scope|
|
||||
previous_value = @existing.send(scope)
|
||||
|
||||
# Assume the scope is a foreign key if the field is nil
|
||||
previous_value ||= 0
|
||||
|
||||
next_value = previous_value.next
|
||||
|
||||
@subject.send("#{scope}=", next_value)
|
||||
|
||||
if allows_value_of(existing_value, @expected_message)
|
||||
@negative_failure_message <<
|
||||
" (with different value of #{scope})"
|
||||
true
|
||||
else
|
||||
@failure_message << " (with different value of #{scope})"
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
def existing_value
|
||||
value = @existing.send(@attribute)
|
||||
value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,54 +0,0 @@
|
|||
module Shoulda # :nodoc:
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
class ValidationMatcher # :nodoc:
|
||||
|
||||
attr_reader :failure_message
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
@negative_failure_message || @failure_message
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allows_value_of(value, message = nil)
|
||||
allow = AllowValueMatcher.
|
||||
new(value).
|
||||
for(@attribute).
|
||||
with_message(message)
|
||||
if allow.matches?(@subject)
|
||||
@negative_failure_message = allow.failure_message
|
||||
true
|
||||
else
|
||||
@failure_message = allow.negative_failure_message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def disallows_value_of(value, message = nil)
|
||||
disallow = AllowValueMatcher.
|
||||
new(value).
|
||||
for(@attribute).
|
||||
with_message(message)
|
||||
if disallow.matches?(@subject)
|
||||
@failure_message = disallow.negative_failure_message
|
||||
false
|
||||
else
|
||||
@negative_failure_message = disallow.failure_message
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# :enddoc:
|
||||
|
||||
if defined?(::ActiveRecord)
|
||||
require 'shoulda/active_record'
|
||||
module RSpec::Matchers
|
||||
include Shoulda::ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(::ActionController)
|
||||
require 'shoulda/action_controller'
|
||||
module RSpec::Rails::ControllerExampleGroup
|
||||
include Shoulda::ActionController
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(::ActionMailer)
|
||||
require 'shoulda/action_mailer'
|
||||
module RSpec::Rails::MailerExampleGroup
|
||||
include Shoulda::ActionMailer
|
||||
end
|
||||
end
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
# :enddoc:
|
||||
|
||||
if defined?(ActionController)
|
||||
require 'shoulda/action_controller'
|
||||
|
||||
class ActionController::TestCase
|
||||
include Shoulda::ActionController
|
||||
extend Shoulda::ActionController
|
||||
|
||||
def subject
|
||||
@controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActionMailer)
|
||||
require 'shoulda/action_mailer'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::ActionMailer
|
||||
extend Shoulda::ActionMailer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
require 'shoulda/active_record'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::ActiveRecord
|
||||
extend Shoulda::ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
38
lib/shoulda/matchers/action_controller.rb
Normal file
38
lib/shoulda/matchers/action_controller.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
require 'shoulda/matchers/action_controller/assign_to_matcher'
|
||||
require 'shoulda/matchers/action_controller/filter_param_matcher'
|
||||
require 'shoulda/matchers/action_controller/set_the_flash_matcher'
|
||||
require 'shoulda/matchers/action_controller/render_with_layout_matcher'
|
||||
require 'shoulda/matchers/action_controller/respond_with_matcher'
|
||||
require 'shoulda/matchers/action_controller/respond_with_content_type_matcher'
|
||||
require 'shoulda/matchers/action_controller/set_session_matcher'
|
||||
require 'shoulda/matchers/action_controller/route_matcher'
|
||||
require 'shoulda/matchers/action_controller/redirect_to_matcher'
|
||||
require 'shoulda/matchers/action_controller/render_template_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 assign_to(:user) }
|
||||
# it { should respond_with(:success) }
|
||||
# it { should render_template(:show) }
|
||||
# it { should not_set_the_flash) }
|
||||
#
|
||||
# it "should do something else really cool" do
|
||||
# assigns[:user].id.should == 1
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the show action
|
||||
module ActionController
|
||||
end
|
||||
end
|
||||
end
|
114
lib/shoulda/matchers/action_controller/assign_to_matcher.rb
Normal file
114
lib/shoulda/matchers/action_controller/assign_to_matcher.rb
Normal file
|
@ -0,0 +1,114 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that the controller assigned to the named instance variable.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_kind_of</tt> - The expected class of the instance variable
|
||||
# being checked.
|
||||
# * <tt>with</tt> - The value that should be assigned.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should assign_to(:user) }
|
||||
# it { should_not assign_to(:user) }
|
||||
# it { should assign_to(:user).with_kind_of(User) }
|
||||
# it { should assign_to(:user).with(@user) }
|
||||
def assign_to(variable)
|
||||
AssignToMatcher.new(variable)
|
||||
end
|
||||
|
||||
class AssignToMatcher # :nodoc:
|
||||
|
||||
def initialize(variable)
|
||||
@variable = variable.to_s
|
||||
@check_value = false
|
||||
end
|
||||
|
||||
def with_kind_of(expected_class)
|
||||
@expected_class = expected_class
|
||||
self
|
||||
end
|
||||
|
||||
def with(expected_value = nil, &block)
|
||||
@check_value = true
|
||||
@expected_value = expected_value
|
||||
@expectation_block = block
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
@expected_value = @context.instance_eval(&@expectation_block) if @expectation_block
|
||||
assigned_value? && kind_of_expected_class? && equal_to_expected_value?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
description = "assign @#{@variable}"
|
||||
description << " with a kind of #{@expected_class}" if @expected_class
|
||||
description
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assigned_value?
|
||||
if !@controller.instance_variables.include?("@#{@variable}")
|
||||
@failure_message =
|
||||
"Expected action to assign a value for @#{@variable}"
|
||||
false
|
||||
else
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign a value for @#{@variable}, " <<
|
||||
"but it was assigned to #{assigned_value.inspect}"
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def kind_of_expected_class?
|
||||
return true unless @expected_class
|
||||
if assigned_value.kind_of?(@expected_class)
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign a kind of #{@expected_class} " <<
|
||||
"for #{@variable}, but got one anyway"
|
||||
true
|
||||
else
|
||||
@failure_message =
|
||||
"Expected action to assign a kind of #{@expected_class} " <<
|
||||
"for #{@variable}, but got #{@variable.inspect} " <<
|
||||
"(#{@variable.class.name})"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def equal_to_expected_value?
|
||||
return true unless @check_value
|
||||
if @expected_value == assigned_value
|
||||
@negative_failure_message =
|
||||
"Didn't expect action to assign #{@expected_value.inspect} " <<
|
||||
"for #{@variable}, but got it anyway"
|
||||
true
|
||||
else
|
||||
@failure_message =
|
||||
"Expected action to assign #{@expected_value.inspect} " <<
|
||||
"for #{@variable}, but got #{assigned_value.inspect}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_value
|
||||
@controller.instance_variable_get("@#{@variable}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that filter_parameter_logging is set for the specified key.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should filter_param(:password) }
|
||||
def filter_param(key)
|
||||
FilterParamMatcher.new(key)
|
||||
end
|
||||
|
||||
class FilterParamMatcher # :nodoc:
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
filters_key?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{@key} to be filtered; filtered keys: #{filtered_keys.join(', ')}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{@key} to be filtered"
|
||||
end
|
||||
|
||||
def description
|
||||
"filter #{@key}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filters_key?
|
||||
filtered_keys.include?(@key)
|
||||
end
|
||||
|
||||
def filtered_keys
|
||||
Rails.application.config.filter_parameters.map { |filter| filter.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller redirected to the given url.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(url_or_description, context, &block)
|
||||
if block
|
||||
@url_block = block
|
||||
@location = @url_or_description
|
||||
else
|
||||
@url = url_or_description
|
||||
@location = @url
|
||||
end
|
||||
@context = context
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
redirects_to_url?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"redirect to #{@location}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirects_to_url?
|
||||
@url = @context.instance_eval(&@url_block) if @url_block
|
||||
begin
|
||||
@context.send(:assert_redirected_to, @url)
|
||||
@negative_failure_message = "Didn't expect to redirect to #{@url}"
|
||||
true
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller rendered the given template.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should render_template(:show) }
|
||||
def render_template(template)
|
||||
RenderTemplateMatcher.new(template, self)
|
||||
end
|
||||
|
||||
class RenderTemplateMatcher # :nodoc:
|
||||
|
||||
def initialize(template, context)
|
||||
@template = template.to_s
|
||||
@context = context
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
renders_template?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"render template #{@template}"
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def renders_template?
|
||||
begin
|
||||
@context.send(:assert_template, @template)
|
||||
@negative_failure_message = "Didn't expect to render #{@template}"
|
||||
true
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,99 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that the controller rendered with the given layout.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(expected_layout)
|
||||
@expected_layout = expected_layout.to_s unless expected_layout.nil?
|
||||
end
|
||||
|
||||
# Used to provide access to layouts recorded by
|
||||
# ActionController::TemplateAssertions in Rails 3
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
rendered_with_layout? && rendered_with_expected_layout?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "render with "
|
||||
if @expected_layout.nil?
|
||||
description << "a layout"
|
||||
else
|
||||
description << "the #{@expected_layout.inspect} layout"
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rendered_with_layout?
|
||||
!rendered_layouts.empty?
|
||||
end
|
||||
|
||||
def rendered_with_expected_layout?
|
||||
return true if @expected_layout.nil?
|
||||
rendered_layouts.include?(@expected_layout)
|
||||
end
|
||||
|
||||
def rendered_layouts
|
||||
if recorded_layouts
|
||||
recorded_layouts.keys.compact.map { |layout| layout.sub(%r{^layouts/}, '') }
|
||||
else
|
||||
layout = @controller.response.layout
|
||||
if layout.nil?
|
||||
[]
|
||||
else
|
||||
[layout.split('/').last]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def recorded_layouts
|
||||
if @context
|
||||
@context.instance_variable_get('@layouts')
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"to #{description}"
|
||||
end
|
||||
|
||||
def result
|
||||
if rendered_with_layout?
|
||||
"rendered with " <<
|
||||
rendered_layouts.map { |layout| layout.inspect }.join(", ")
|
||||
else
|
||||
"rendered without a layout"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller responded with expected 'response' content type.
|
||||
#
|
||||
# You can pass an explicit content type such as 'application/rss+xml'
|
||||
# or its symbolic equivalent :rss
|
||||
# or a regular expression such as /rss/
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# it { should respond_with_content_type(:xml) }
|
||||
# it { should respond_with_content_type(:csv) }
|
||||
# it { should respond_with_content_type(:atom) }
|
||||
# it { should respond_with_content_type(:yaml) }
|
||||
# it { should respond_with_content_type(:text) }
|
||||
# it { should respond_with_content_type('application/rss+xml') }
|
||||
# it { should respond_with_content_type(/json/) }
|
||||
def respond_with_content_type(content_type)
|
||||
RespondWithContentTypeMatcher.new(content_type)
|
||||
end
|
||||
|
||||
class RespondWithContentTypeMatcher # :nodoc:
|
||||
|
||||
def initialize(content_type)
|
||||
@content_type = if content_type.is_a?(Symbol)
|
||||
lookup_by_extension(content_type)
|
||||
else
|
||||
content_type
|
||||
end
|
||||
end
|
||||
|
||||
def description
|
||||
"respond with content type of #{@content_type}"
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
if @content_type.is_a?(Regexp)
|
||||
response_content_type =~ @content_type
|
||||
else
|
||||
response_content_type == @content_type
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def response_content_type
|
||||
@controller.response.content_type.to_s
|
||||
end
|
||||
|
||||
def lookup_by_extension(extension)
|
||||
Mime::Type.lookup_by_extension(extension.to_s).to_s
|
||||
end
|
||||
|
||||
def expectation
|
||||
"content type to be #{@content_type}, " <<
|
||||
"but was #{response_content_type}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,85 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures a controller responded with expected 'response' 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.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(status)
|
||||
@status = symbol_to_status_code(status)
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
correct_status_code? || correct_status_code_range?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"respond with #{@status}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def correct_status_code?
|
||||
response_code == @status
|
||||
end
|
||||
|
||||
def correct_status_code_range?
|
||||
@status.is_a?(Range) &&
|
||||
@status.include?(response_code)
|
||||
end
|
||||
|
||||
def response_code
|
||||
@controller.response.response_code
|
||||
end
|
||||
|
||||
def symbol_to_status_code(potential_symbol)
|
||||
case potential_symbol
|
||||
when :success then 200
|
||||
when :redirect then 300..399
|
||||
when :missing then 404
|
||||
when :error then 500..599
|
||||
when Symbol
|
||||
if defined?(::Rack::Utils::SYMBOL_TO_STATUS_CODE)
|
||||
::Rack::Utils::SYMBOL_TO_STATUS_CODE[potential_symbol]
|
||||
else
|
||||
::ActionController::Base::SYMBOL_TO_STATUS_CODE[potential_symbol]
|
||||
end
|
||||
else
|
||||
potential_symbol
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"response to be a #{@status}, but was #{response_code}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
93
lib/shoulda/matchers/action_controller/route_matcher.rb
Normal file
93
lib/shoulda/matchers/action_controller/route_matcher.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that requesting +path+ using +method+ routes to +options+.
|
||||
#
|
||||
# If you don't specify a controller, it will use the controller from the
|
||||
# example group.
|
||||
#
|
||||
# +to_param+ is called on the +options+ given.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# it { should route(:get, "/posts").
|
||||
# to(:controller => :posts, :action => :index) }
|
||||
# it { should route(:get, "/posts/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(:edit, "/posts/1").to(:action => :show, :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) }
|
||||
def route(method, path)
|
||||
RouteMatcher.new(method, path, self)
|
||||
end
|
||||
|
||||
class RouteMatcher # :nodoc:
|
||||
|
||||
def initialize(method, path, context)
|
||||
@method = method
|
||||
@path = path
|
||||
@context = context
|
||||
end
|
||||
|
||||
def to(params)
|
||||
@params = params
|
||||
stringify_params!
|
||||
self
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
guess_controller!
|
||||
route_recognized?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"route #{@method.to_s.upcase} #{@path} to/from #{@params.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def guess_controller!
|
||||
@params[:controller] ||= @controller.controller_path
|
||||
end
|
||||
|
||||
def stringify_params!
|
||||
@params.each do |key, value|
|
||||
@params[key] = value.is_a?(Array) ? value.collect {|v| v.to_param } : value.to_param
|
||||
end
|
||||
end
|
||||
|
||||
def route_recognized?
|
||||
begin
|
||||
@context.send(:assert_routing,
|
||||
{ :method => @method, :path => @path },
|
||||
@params)
|
||||
|
||||
@negative_failure_message = "Didn't expect to #{description}"
|
||||
true
|
||||
rescue ::ActionController::RoutingError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
rescue Test::Unit::AssertionFailedError => error
|
||||
@failure_message = error.message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,98 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionController # :nodoc:
|
||||
|
||||
# Ensures that a session key was set to the expected value.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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:
|
||||
|
||||
def initialize(key)
|
||||
@key = key.to_s
|
||||
end
|
||||
|
||||
def to(value = nil, &block)
|
||||
@value = value
|
||||
@value_block = block
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
@value = @context.instance_eval(&@value_block) if @value_block
|
||||
(assigned_value? && assigned_correct_value?) || cleared_value?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Didn't expect #{expectation}, but #{result}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "set session variable #{@key.inspect}"
|
||||
description << " to #{@value.inspect}" if defined?(@value)
|
||||
description
|
||||
end
|
||||
|
||||
def in_context(context)
|
||||
@context = context
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assigned_value?
|
||||
!assigned_value.nil?
|
||||
end
|
||||
|
||||
def cleared_value?
|
||||
defined?(@value) && @value.nil? && assigned_value.nil?
|
||||
end
|
||||
|
||||
def assigned_correct_value?
|
||||
return true if @value.nil?
|
||||
assigned_value == @value
|
||||
end
|
||||
|
||||
def assigned_value
|
||||
session[@key]
|
||||
end
|
||||
|
||||
def session
|
||||
if @controller.request.respond_to?(:session)
|
||||
@controller.request.session.to_hash
|
||||
else
|
||||
@controller.response.session.data
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
expectation = "session variable #{@key} to be set"
|
||||
expectation << " to #{@value.inspect}" if @value
|
||||
expectation
|
||||
end
|
||||
|
||||
def result
|
||||
if session.empty?
|
||||
"no session variables were set"
|
||||
else
|
||||
"the session was #{session.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,94 @@
|
|||
module Shoulda # :nodoc:
|
||||
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).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# 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.to(/logged in/i).now }
|
||||
# it { should_not set_the_flash }
|
||||
def set_the_flash
|
||||
SetTheFlashMatcher.new
|
||||
end
|
||||
|
||||
class SetTheFlashMatcher # :nodoc:
|
||||
|
||||
def to(value)
|
||||
@value = value
|
||||
self
|
||||
end
|
||||
|
||||
def now
|
||||
@now = true
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(controller)
|
||||
@controller = controller
|
||||
sets_the_flash? && string_value_matches? && regexp_value_matches?
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
description = "set the flash"
|
||||
description << " to #{@value.inspect}" unless @value.nil?
|
||||
description
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sets_the_flash?
|
||||
!flash.blank?
|
||||
end
|
||||
|
||||
def string_value_matches?
|
||||
return true unless String === @value
|
||||
flash.values.any? {|value| value == @value }
|
||||
end
|
||||
|
||||
def regexp_value_matches?
|
||||
return true unless Regexp === @value
|
||||
flash.values.any? {|value| value =~ @value }
|
||||
end
|
||||
|
||||
def flash
|
||||
return @flash if @flash
|
||||
@flash = @controller.flash.dup
|
||||
@flash.sweep unless @now
|
||||
@flash
|
||||
end
|
||||
|
||||
def expectation
|
||||
expectation = "the flash#{".now" if @now} to be set"
|
||||
expectation << " to #{@value.inspect}" unless @value.nil?
|
||||
expectation << ", but #{flash_description}"
|
||||
expectation
|
||||
end
|
||||
|
||||
def flash_description
|
||||
if flash.blank?
|
||||
"no flash was set"
|
||||
else
|
||||
"was #{flash.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
22
lib/shoulda/matchers/action_mailer.rb
Normal file
22
lib/shoulda/matchers/action_mailer.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
require 'shoulda/matchers/action_mailer/have_sent_email'
|
||||
|
||||
module Shoulda
|
||||
module Matchers
|
||||
# = Matchers for your mailers
|
||||
#
|
||||
# This matcher will test that email is sent properly
|
||||
#
|
||||
# describe User do
|
||||
# it { should have_sent_email.with_subject(/is spam$/) }
|
||||
# it { should have_sent_email.from('do-not-reply@example.com') }
|
||||
# it { should have_sent_email.with_body(/is spam\./) }
|
||||
# it { should have_sent_email.to('myself@me.com') }
|
||||
# it { should have_sent_email.with_subject(/spam/).
|
||||
# from('do-not-reply@example.com').
|
||||
# with_body(/spam/).
|
||||
# to('myself@me.com') }
|
||||
# end
|
||||
module ActionMailer
|
||||
end
|
||||
end
|
||||
end
|
110
lib/shoulda/matchers/action_mailer/have_sent_email.rb
Normal file
110
lib/shoulda/matchers/action_mailer/have_sent_email.rb
Normal file
|
@ -0,0 +1,110 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActionMailer # :nodoc:
|
||||
|
||||
# The right email is sent.
|
||||
#
|
||||
# it { should have_sent_email.with_subject(/is spam$/) }
|
||||
# it { should have_sent_email.from('do-not-reply@example.com') }
|
||||
# it { should have_sent_email.with_body(/is spam\./) }
|
||||
# it { should have_sent_email.to('myself@me.com') }
|
||||
# it { should have_sent_email.with_subject(/spam/).
|
||||
# from('do-not-reply@example.com').
|
||||
# with_body(/spam/).
|
||||
# to('myself@me.com') }
|
||||
def have_sent_email
|
||||
HaveSentEmailMatcher.new
|
||||
end
|
||||
|
||||
class HaveSentEmailMatcher # :nodoc:
|
||||
|
||||
def initialize
|
||||
end
|
||||
|
||||
def with_subject(email_subject)
|
||||
@email_subject = email_subject
|
||||
self
|
||||
end
|
||||
|
||||
def from(sender)
|
||||
@sender = sender
|
||||
self
|
||||
end
|
||||
|
||||
def with_body(body)
|
||||
@body = body
|
||||
self
|
||||
end
|
||||
|
||||
def to(recipient)
|
||||
@recipient = recipient
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
::ActionMailer::Base.deliveries.each do |mail|
|
||||
@subject_failed = !regexp_or_string_match(mail.subject, @email_subject) if @email_subject
|
||||
@body_failed = !regexp_or_string_match(mail.body, @body) if @body
|
||||
@sender_failed = !regexp_or_string_match_in_array(mail.from, @sender) if @sender
|
||||
@recipient_failed = !regexp_or_string_match_in_array(mail.to, @recipient) if @recipient
|
||||
return true unless anything_failed?
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"send an email"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expectation
|
||||
expectation = "sent email"
|
||||
expectation << " with subject #{@email_subject.inspect}" if @subject_failed
|
||||
expectation << " with body #{@body.inspect}" if @body_failed
|
||||
expectation << " from #{@sender.inspect}" if @sender_failed
|
||||
expectation << " to #{@recipient.inspect}" if @recipient_failed
|
||||
expectation << "\nDeliveries:\n#{inspect_deliveries}"
|
||||
end
|
||||
|
||||
def inspect_deliveries
|
||||
::ActionMailer::Base.deliveries.map do |delivery|
|
||||
"#{delivery.subject.inspect} to #{delivery.to.inspect}"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def anything_failed?
|
||||
@subject_failed || @body_failed || @sender_failed || @recipient_failed
|
||||
end
|
||||
|
||||
def regexp_or_string_match(a_string, a_regexp_or_string)
|
||||
case a_regexp_or_string
|
||||
when Regexp
|
||||
a_string =~ a_regexp_or_string
|
||||
when String
|
||||
a_string == a_regexp_or_string
|
||||
end
|
||||
end
|
||||
|
||||
def regexp_or_string_match_in_array(an_array, a_regexp_or_string)
|
||||
case a_regexp_or_string
|
||||
when Regexp
|
||||
an_array.any? { |string| string =~ a_regexp_or_string }
|
||||
when String
|
||||
an_array.include?(a_regexp_or_string)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
42
lib/shoulda/matchers/active_record.rb
Normal file
42
lib/shoulda/matchers/active_record.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
require 'shoulda/matchers/active_record/helpers'
|
||||
require 'shoulda/matchers/active_record/validation_matcher'
|
||||
require 'shoulda/matchers/active_record/allow_value_matcher'
|
||||
require 'shoulda/matchers/active_record/ensure_length_of_matcher'
|
||||
require 'shoulda/matchers/active_record/ensure_inclusion_of_matcher'
|
||||
require 'shoulda/matchers/active_record/validate_presence_of_matcher'
|
||||
require 'shoulda/matchers/active_record/validate_format_of_matcher'
|
||||
require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher'
|
||||
require 'shoulda/matchers/active_record/validate_acceptance_of_matcher'
|
||||
require 'shoulda/matchers/active_record/validate_numericality_of_matcher'
|
||||
require 'shoulda/matchers/active_record/association_matcher'
|
||||
require 'shoulda/matchers/active_record/have_db_column_matcher'
|
||||
require 'shoulda/matchers/active_record/have_db_index_matcher'
|
||||
require 'shoulda/matchers/active_record/have_readonly_attribute_matcher'
|
||||
require 'shoulda/matchers/active_record/allow_mass_assignment_of_matcher'
|
||||
|
||||
|
||||
module Shoulda
|
||||
module Matchers
|
||||
# = Matchers for your active record models
|
||||
#
|
||||
# These matchers will test most of the validations and associations for your
|
||||
# ActiveRecord models.
|
||||
#
|
||||
# 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 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
|
||||
end
|
|
@ -0,0 +1,83 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the attribute can be set on mass update.
|
||||
#
|
||||
# it { should_not allow_mass_assignment_of(:password) }
|
||||
# it { should allow_mass_assignment_of(:first_name) }
|
||||
#
|
||||
def allow_mass_assignment_of(value)
|
||||
AllowMassAssignmentOfMatcher.new(value)
|
||||
end
|
||||
|
||||
class AllowMassAssignmentOfMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute.to_s
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
if attr_mass_assignable?
|
||||
if whitelisting?
|
||||
@negative_failure_message = "#{@attribute} was made accessible"
|
||||
else
|
||||
if protected_attributes.empty?
|
||||
@negative_failure_message = "no attributes were protected"
|
||||
else
|
||||
@negative_failure_message = "#{class_name} is protecting " <<
|
||||
"#{protected_attributes.to_a.to_sentence}, " <<
|
||||
"but not #{@attribute}."
|
||||
end
|
||||
end
|
||||
true
|
||||
else
|
||||
if whitelisting?
|
||||
@failure_message =
|
||||
"Expected #{@attribute} to be accessible"
|
||||
else
|
||||
@failure_message =
|
||||
"Did not expect #{@attribute} to be protected"
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"allow mass assignment of #{@attribute}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def protected_attributes
|
||||
@protected_attributes ||= (@subject.class.protected_attributes || [])
|
||||
end
|
||||
|
||||
def accessible_attributes
|
||||
@accessible_attributes ||= (@subject.class.accessible_attributes || [])
|
||||
end
|
||||
|
||||
def whitelisting?
|
||||
!accessible_attributes.empty?
|
||||
end
|
||||
|
||||
def attr_mass_assignable?
|
||||
if whitelisting?
|
||||
accessible_attributes.include?(@attribute)
|
||||
else
|
||||
!protected_attributes.include?(@attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
110
lib/shoulda/matchers/active_record/allow_value_matcher.rb
Normal file
110
lib/shoulda/matchers/active_record/allow_value_matcher.rb
Normal file
|
@ -0,0 +1,110 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the attribute can be set to the given value.
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# Example:
|
||||
# it { should_not allow_value('bad').for(:isbn) }
|
||||
# it { should allow_value("isbn 1 2345 6789 0").for(:isbn) }
|
||||
#
|
||||
def allow_value(value)
|
||||
AllowValueMatcher.new(value)
|
||||
end
|
||||
|
||||
class AllowValueMatcher # :nodoc:
|
||||
include Helpers
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
@attribute = attribute
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(instance)
|
||||
@instance = instance
|
||||
if Symbol === @expected_message
|
||||
@expected_message = default_error_message(@expected_message)
|
||||
end
|
||||
@instance.send("#{@attribute}=", @value)
|
||||
!errors_match?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Did not expect #{expectation}, got error: #{@matched_error}"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Expected #{expectation}, got #{error_description}"
|
||||
end
|
||||
|
||||
def description
|
||||
"allow #{@attribute} to be set to #{@value.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def errors_match?
|
||||
@instance.valid?
|
||||
@errors = errors_for_attribute(@instance, @attribute)
|
||||
@errors = [@errors] unless @errors.is_a?(Array)
|
||||
@expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?)
|
||||
end
|
||||
|
||||
def errors_for_attribute(instance, attribute)
|
||||
if instance.errors.respond_to?(:[])
|
||||
instance.errors[attribute]
|
||||
else
|
||||
instance.errors.on(attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def errors_match_regexp?
|
||||
if Regexp === @expected_message
|
||||
@matched_error = @errors.detect { |e| e =~ @expected_message }
|
||||
!@matched_error.nil?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def errors_match_string?
|
||||
if @errors.include?(@expected_message)
|
||||
@matched_error = @expected_message
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def expectation
|
||||
"errors " <<
|
||||
(@expected_message ? "to include #{@expected_message.inspect} " : "") <<
|
||||
"when #{@attribute} is set to #{@value.inspect}"
|
||||
end
|
||||
|
||||
def error_description
|
||||
if @instance.errors.empty?
|
||||
"no errors"
|
||||
else
|
||||
"errors: #{pretty_error_messages(@instance)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
226
lib/shoulda/matchers/active_record/association_matcher.rb
Normal file
226
lib/shoulda/matchers/active_record/association_matcher.rb
Normal file
|
@ -0,0 +1,226 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensure that the belongs_to relationship exists.
|
||||
#
|
||||
# it { should belong_to(:parent) }
|
||||
#
|
||||
def belong_to(name)
|
||||
AssociationMatcher.new(:belongs_to, name)
|
||||
end
|
||||
|
||||
# Ensures that the has_many relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>through</tt> - association name for <tt>has_many :through</tt>
|
||||
# * <tt>dependent</tt> - tests that the association makes use of the
|
||||
# dependent option.
|
||||
#
|
||||
# Example:
|
||||
# it { should have_many(:friends) }
|
||||
# it { should have_many(:enemies).through(:friends) }
|
||||
# it { should have_many(:enemies).dependent(:destroy) }
|
||||
#
|
||||
def have_many(name)
|
||||
AssociationMatcher.new(:has_many, name)
|
||||
end
|
||||
|
||||
# Ensure that the has_one relationship exists. Will also test that the
|
||||
# associated table has the required columns. Works with polymorphic
|
||||
# associations.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:dependent</tt> - tests that the association makes use of the
|
||||
# dependent option.
|
||||
#
|
||||
# Example:
|
||||
# it { should have_one(:god) } # unless hindu
|
||||
#
|
||||
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.
|
||||
#
|
||||
# it { should have_and_belong_to_many(:posts) }
|
||||
#
|
||||
def have_and_belong_to_many(name)
|
||||
AssociationMatcher.new(:has_and_belongs_to_many, name)
|
||||
end
|
||||
|
||||
class AssociationMatcher # :nodoc:
|
||||
def initialize(macro, name)
|
||||
@macro = macro
|
||||
@name = name
|
||||
end
|
||||
|
||||
def through(through)
|
||||
@through = through
|
||||
self
|
||||
end
|
||||
|
||||
def dependent(dependent)
|
||||
@dependent = dependent
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
association_exists? &&
|
||||
macro_correct? &&
|
||||
foreign_key_exists? &&
|
||||
through_association_valid? &&
|
||||
dependent_correct? &&
|
||||
join_table_exists?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
description = "#{macro_description} #{@name}"
|
||||
description += " through #{@through}" if @through
|
||||
description += " dependent => #{@dependent}" if @dependent
|
||||
description
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def association_exists?
|
||||
if reflection.nil?
|
||||
@missing = "no association called #{@name}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def macro_correct?
|
||||
if reflection.macro == @macro
|
||||
true
|
||||
else
|
||||
@missing = "actual association type was #{reflection.macro}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def foreign_key_exists?
|
||||
!(belongs_foreign_key_missing? || has_foreign_key_missing?)
|
||||
end
|
||||
|
||||
def belongs_foreign_key_missing?
|
||||
@macro == :belongs_to && !class_has_foreign_key?(model_class)
|
||||
end
|
||||
|
||||
def has_foreign_key_missing?
|
||||
[:has_many, :has_one].include?(@macro) &&
|
||||
!through? &&
|
||||
!class_has_foreign_key?(associated_class)
|
||||
end
|
||||
|
||||
def through_association_valid?
|
||||
@through.nil? || (through_association_exists? && through_association_correct?)
|
||||
end
|
||||
|
||||
def through_association_exists?
|
||||
if through_reflection.nil?
|
||||
@missing = "#{model_class.name} does not have any relationship to #{@through}"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def through_association_correct?
|
||||
if @through == reflection.options[:through]
|
||||
true
|
||||
else
|
||||
@missing = "Expected #{model_class.name} to have #{@name} through #{@through}, " <<
|
||||
"but got it through #{reflection.options[:through]}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def dependent_correct?
|
||||
if @dependent.nil? || @dependent.to_s == reflection.options[:dependent].to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{@name} should have #{@dependent} dependency"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def join_table_exists?
|
||||
if @macro != :has_and_belongs_to_many ||
|
||||
::ActiveRecord::Base.connection.tables.include?(join_table.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "join table #{join_table} doesn't exist"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def class_has_foreign_key?(klass)
|
||||
if klass.column_names.include?(foreign_key.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "#{klass} does not have a #{foreign_key} foreign key."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def join_table
|
||||
reflection.options[:join_table]
|
||||
end
|
||||
|
||||
def associated_class
|
||||
reflection.klass
|
||||
end
|
||||
|
||||
def foreign_key
|
||||
reflection.primary_key_name
|
||||
end
|
||||
|
||||
def through?
|
||||
reflection.options[:through]
|
||||
end
|
||||
|
||||
def reflection
|
||||
@reflection ||= model_class.reflect_on_association(@name)
|
||||
end
|
||||
|
||||
def through_reflection
|
||||
@through_reflection ||= model_class.reflect_on_association(@through)
|
||||
end
|
||||
|
||||
def expectation
|
||||
"#{model_class.name} to have a #{@macro} association called #{@name}"
|
||||
end
|
||||
|
||||
def macro_description
|
||||
case @macro.to_s
|
||||
when 'belongs_to' then 'belong to'
|
||||
when 'has_many' then 'have many'
|
||||
when 'has_one' then 'have one'
|
||||
when 'has_and_belongs_to_many' then
|
||||
'have and belong to many'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensure that the attribute's value is in the range specified
|
||||
#
|
||||
# Options:
|
||||
# * <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.
|
||||
#
|
||||
# Example:
|
||||
# it { should ensure_inclusion_of(:age).in_range(0..100) }
|
||||
#
|
||||
def ensure_inclusion_of(attr)
|
||||
EnsureInclusionOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class EnsureInclusionOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def in_range(range)
|
||||
@range = range
|
||||
@minimum = range.first
|
||||
@maximum = range.last
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
if message
|
||||
@low_message = message
|
||||
@high_message = message
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def with_low_message(message)
|
||||
@low_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def with_high_message(message)
|
||||
@high_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
"ensure inclusion of #{@attribute} in #{@range.inspect}"
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
|
||||
@low_message ||= :inclusion
|
||||
@high_message ||= :inclusion
|
||||
|
||||
disallows_lower_value &&
|
||||
allows_minimum_value &&
|
||||
disallows_higher_value &&
|
||||
allows_maximum_value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disallows_lower_value
|
||||
@minimum == 0 || disallows_value_of(@minimum - 1, @low_message)
|
||||
end
|
||||
|
||||
def disallows_higher_value
|
||||
disallows_value_of(@maximum + 1, @high_message)
|
||||
end
|
||||
|
||||
def allows_minimum_value
|
||||
allows_value_of(@minimum, @low_message)
|
||||
end
|
||||
|
||||
def allows_maximum_value
|
||||
allows_value_of(@maximum, @high_message)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
141
lib/shoulda/matchers/active_record/ensure_length_of_matcher.rb
Normal file
141
lib/shoulda/matchers/active_record/ensure_length_of_matcher.rb
Normal file
|
@ -0,0 +1,141 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the length of the attribute is validated.
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# 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:
|
||||
include Helpers
|
||||
|
||||
def is_at_least(length)
|
||||
@minimum = length
|
||||
@short_message ||= :too_short
|
||||
self
|
||||
end
|
||||
|
||||
def is_at_most(length)
|
||||
@maximum = length
|
||||
@long_message ||= :too_long
|
||||
self
|
||||
end
|
||||
|
||||
def is_equal_to(length)
|
||||
@minimum = length
|
||||
@maximum = length
|
||||
@short_message ||= :wrong_length
|
||||
self
|
||||
end
|
||||
|
||||
def with_short_message(message)
|
||||
@short_message = message if message
|
||||
self
|
||||
end
|
||||
alias_method :with_message, :with_short_message
|
||||
|
||||
def with_long_message(message)
|
||||
@long_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
description = "ensure #{@attribute} has a length "
|
||||
if @minimum && @maximum
|
||||
if @minimum == @maximum
|
||||
description << "of exactly #{@minimum}"
|
||||
else
|
||||
description << "between #{@minimum} and #{@maximum}"
|
||||
end
|
||||
else
|
||||
description << "of at least #{@minimum}" if @minimum
|
||||
description << "of at most #{@maximum}" if @maximum
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
translate_messages!
|
||||
disallows_lower_length &&
|
||||
allows_minimum_length &&
|
||||
((@minimum == @maximum) ||
|
||||
(disallows_higher_length &&
|
||||
allows_maximum_length))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def translate_messages!
|
||||
if Symbol === @short_message
|
||||
@short_message = default_error_message(@short_message,
|
||||
:count => @minimum)
|
||||
end
|
||||
|
||||
if Symbol === @long_message
|
||||
@long_message = default_error_message(@long_message,
|
||||
:count => @maximum)
|
||||
end
|
||||
end
|
||||
|
||||
def disallows_lower_length
|
||||
@minimum == 0 ||
|
||||
@minimum.nil? ||
|
||||
disallows_length_of(@minimum - 1, @short_message)
|
||||
end
|
||||
|
||||
def disallows_higher_length
|
||||
@maximum.nil? || disallows_length_of(@maximum + 1, @long_message)
|
||||
end
|
||||
|
||||
def allows_minimum_length
|
||||
allows_length_of(@minimum, @short_message)
|
||||
end
|
||||
|
||||
def allows_maximum_length
|
||||
allows_length_of(@maximum, @long_message)
|
||||
end
|
||||
|
||||
def allows_length_of(length, message)
|
||||
length.nil? || allows_value_of(string_of_length(length), message)
|
||||
end
|
||||
|
||||
def disallows_length_of(length, message)
|
||||
length.nil? || disallows_value_of(string_of_length(length), message)
|
||||
end
|
||||
|
||||
def string_of_length(length)
|
||||
'x' * length
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
169
lib/shoulda/matchers/active_record/have_db_column_matcher.rb
Normal file
169
lib/shoulda/matchers/active_record/have_db_column_matcher.rb
Normal file
|
@ -0,0 +1,169 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures the database column exists.
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
# 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) }
|
||||
#
|
||||
def have_db_column(column)
|
||||
HaveDbColumnMatcher.new(:have_db_column, column)
|
||||
end
|
||||
|
||||
class HaveDbColumnMatcher # :nodoc:
|
||||
def initialize(macro, column)
|
||||
@macro = macro
|
||||
@column = column
|
||||
end
|
||||
|
||||
def of_type(column_type)
|
||||
@column_type = column_type
|
||||
self
|
||||
end
|
||||
|
||||
def with_options(opts = {})
|
||||
@precision = opts[:precision]
|
||||
@limit = opts[:limit]
|
||||
@default = opts[:default]
|
||||
@null = opts[:null]
|
||||
@scale = opts[:scale]
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
column_exists? &&
|
||||
correct_column_type? &&
|
||||
correct_precision? &&
|
||||
correct_limit? &&
|
||||
correct_default? &&
|
||||
correct_null? &&
|
||||
correct_scale?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
desc = "have db column named #{@column}"
|
||||
desc << " of type #{@column_type}" unless @column_type.nil?
|
||||
desc << " of precision #{@precision}" unless @precision.nil?
|
||||
desc << " of limit #{@limit}" unless @limit.nil?
|
||||
desc << " of default #{@default}" unless @default.nil?
|
||||
desc << " of null #{@null}" unless @null.nil?
|
||||
desc << " of primary #{@primary}" unless @primary.nil?
|
||||
desc << " of scale #{@scale}" unless @scale.nil?
|
||||
desc
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def column_exists?
|
||||
if model_class.column_names.include?(@column.to_s)
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} does not have a db column named #{@column}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_column_type?
|
||||
return true if @column_type.nil?
|
||||
if matched_column.type.to_s == @column_type.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of type #{matched_column.type}, not #{@column_type}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_precision?
|
||||
return true if @precision.nil?
|
||||
if matched_column.precision.to_s == @precision.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of precision #{matched_column.precision}, " <<
|
||||
"not #{@precision}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_limit?
|
||||
return true if @limit.nil?
|
||||
if matched_column.limit.to_s == @limit.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of limit #{matched_column.limit}, " <<
|
||||
"not #{@limit}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_default?
|
||||
return true if @default.nil?
|
||||
if matched_column.default.to_s == @default.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of default #{matched_column.default}, " <<
|
||||
"not #{@default}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_null?
|
||||
return true if @null.nil?
|
||||
if matched_column.null.to_s == @null.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of null #{matched_column.null}, " <<
|
||||
"not #{@null}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def correct_scale?
|
||||
return true if @scale.nil?
|
||||
if matched_column.scale.to_s == @scale.to_s
|
||||
true
|
||||
else
|
||||
@missing = "#{model_class} has a db column named #{@column} " <<
|
||||
"of scale #{matched_column.scale}, not #{@scale}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def matched_column
|
||||
model_class.columns.detect { |each| each.name == @column.to_s }
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def expectation
|
||||
expected = "#{model_class.name} to #{description}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
112
lib/shoulda/matchers/active_record/have_db_index_matcher.rb
Normal file
112
lib/shoulda/matchers/active_record/have_db_index_matcher.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that there are DB indices on the given columns or tuples of
|
||||
# columns.
|
||||
#
|
||||
# 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. Use <tt>nil</tt> if you don't care whether the index is
|
||||
# unique or not. Default = <tt>nil</tt>
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# it { should have_db_index(:age) }
|
||||
# it { should have_db_index([:commentable_type, :commentable_id]) }
|
||||
# it { should have_db_index(:ssn).unique(true) }
|
||||
#
|
||||
def have_db_index(columns)
|
||||
HaveDbIndexMatcher.new(:have_index, columns)
|
||||
end
|
||||
|
||||
class HaveDbIndexMatcher # :nodoc:
|
||||
def initialize(macro, columns)
|
||||
@macro = macro
|
||||
@columns = normalize_columns_to_array(columns)
|
||||
end
|
||||
|
||||
def unique(unique)
|
||||
@unique = unique
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
index_exists? && correct_unique?
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Expected #{expectation} (#{@missing})"
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
"Did not expect #{expectation}"
|
||||
end
|
||||
|
||||
def description
|
||||
"have a #{index_type} index on columns #{@columns.join(' and ')}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def index_exists?
|
||||
! matched_index.nil?
|
||||
end
|
||||
|
||||
def correct_unique?
|
||||
return true if @unique.nil?
|
||||
if matched_index.unique == @unique
|
||||
true
|
||||
else
|
||||
@missing = "#{table_name} has an index named #{matched_index.name} " <<
|
||||
"of unique #{matched_index.unique}, not #{@unique}."
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def matched_index
|
||||
indexes.detect { |each| each.columns == @columns }
|
||||
end
|
||||
|
||||
def model_class
|
||||
@subject.class
|
||||
end
|
||||
|
||||
def table_name
|
||||
model_class.table_name
|
||||
end
|
||||
|
||||
def indexes
|
||||
::ActiveRecord::Base.connection.indexes(table_name)
|
||||
end
|
||||
|
||||
def expectation
|
||||
expected = "#{model_class.name} to #{description}"
|
||||
end
|
||||
|
||||
def index_type
|
||||
case @unique
|
||||
when nil
|
||||
''
|
||||
when false
|
||||
'non-unique'
|
||||
else
|
||||
'unique'
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_columns_to_array(columns)
|
||||
if columns.class == Array
|
||||
columns.collect { |each| each.to_s }
|
||||
else
|
||||
[columns.to_s]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,59 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the attribute cannot be changed once the record has been
|
||||
# created.
|
||||
#
|
||||
# it { should have_readonly_attributes(:password) }
|
||||
#
|
||||
def have_readonly_attribute(value)
|
||||
HaveReadonlyAttributeMatcher.new(value)
|
||||
end
|
||||
|
||||
class HaveReadonlyAttributeMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute.to_s
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
if readonly_attributes.include?(@attribute)
|
||||
@negative_failure_message =
|
||||
"Did not expect #{@attribute} to be read-only"
|
||||
true
|
||||
else
|
||||
if readonly_attributes.empty?
|
||||
@failure_message = "#{class_name} attribute #{@attribute} " <<
|
||||
"is not read-only"
|
||||
else
|
||||
@failure_message = "#{class_name} is making " <<
|
||||
"#{readonly_attributes.to_sentence} " <<
|
||||
"read-only, but not #{@attribute}."
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :failure_message, :negative_failure_message
|
||||
|
||||
def description
|
||||
"make #{@attribute} read-only"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def readonly_attributes
|
||||
@readonly_attributes ||= (@subject.class.readonly_attributes || [])
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
34
lib/shoulda/matchers/active_record/helpers.rb
Normal file
34
lib/shoulda/matchers/active_record/helpers.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
module Helpers
|
||||
def pretty_error_messages(obj) # :nodoc:
|
||||
obj.errors.map do |a, m|
|
||||
msg = "#{a} #{m}"
|
||||
msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
|
||||
end
|
||||
end
|
||||
|
||||
# Helper method that determines the default error message used by Active
|
||||
# Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
|
||||
# introduced I18n module used for localization.
|
||||
#
|
||||
# default_error_message(:blank)
|
||||
# default_error_message(:too_short, :count => 5)
|
||||
# default_error_message(:too_long, :count => 60)
|
||||
def default_error_message(key, values = {})
|
||||
if Object.const_defined?(:I18n) # Rails >= 2.2
|
||||
result = I18n.translate("activerecord.errors.messages.#{key}", values)
|
||||
if result =~ /^translation missing/
|
||||
I18n.translate("errors.messages.#{key}", values)
|
||||
else
|
||||
result
|
||||
end
|
||||
else # Rails <= 2.1.x
|
||||
::ActiveRecord::Errors.default_error_messages[key] % values[:count]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model cannot be saved the given attribute is not
|
||||
# accepted.
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# Example:
|
||||
# it { should validate_acceptance_of(:eula) }
|
||||
#
|
||||
def validate_acceptance_of(attr)
|
||||
ValidateAcceptanceOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :accepted
|
||||
disallows_value_of(false, @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"require #{@attribute} to be accepted"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model is not valid if the given attribute is not
|
||||
# formatted correctly.
|
||||
#
|
||||
# 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>.
|
||||
# * <tt>with(string to test against)</tt>
|
||||
# * <tt>not_with(string to test against)</tt>
|
||||
#
|
||||
# Examples:
|
||||
# it { should validate_format_of(:name).
|
||||
# with('12345').
|
||||
# with_message(/is not optional/) }
|
||||
# it { should validate_format_of(:name).
|
||||
# not_with('12D45').
|
||||
# with_message(/is not optional/) }
|
||||
#
|
||||
def validate_format_of(attr)
|
||||
ValidateFormatOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateFormatOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def initialize(attribute)
|
||||
super
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def with(value)
|
||||
raise "You may not call both with and not_with" if @value_to_fail
|
||||
@value_to_pass = value
|
||||
self
|
||||
end
|
||||
|
||||
def not_with(value)
|
||||
raise "You may not call both with and not_with" if @value_to_pass
|
||||
@value_to_fail = value
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :blank
|
||||
return disallows_value_of(@value_to_fail, @expected_message) if @value_to_fail
|
||||
allows_value_of(@value_to_pass, @expected_message) if @value_to_pass
|
||||
end
|
||||
|
||||
def description
|
||||
"#{@attribute} have a valid format"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensure that the attribute is numeric
|
||||
#
|
||||
# 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>.
|
||||
#
|
||||
# Example:
|
||||
# it { should validate_numericality_of(:age) }
|
||||
#
|
||||
def validate_numericality_of(attr)
|
||||
ValidateNumericalityOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :not_a_number
|
||||
disallows_value_of('abcd', @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"only allow numeric values for #{@attribute}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,60 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model is not valid if the given attribute is not
|
||||
# present.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>with_message</tt> - value the test expects to find in
|
||||
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
|
||||
# Defaults to the translation for <tt>:blank</tt>.
|
||||
#
|
||||
# Examples:
|
||||
# it { should validate_presence_of(:name) }
|
||||
# it { should validate_presence_of(:name).
|
||||
# with_message(/is not optional/) }
|
||||
#
|
||||
def validate_presence_of(attr)
|
||||
ValidatePresenceOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidatePresenceOfMatcher < ValidationMatcher # :nodoc:
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message if message
|
||||
self
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
super(subject)
|
||||
@expected_message ||= :blank
|
||||
disallows_value_of(blank_value, @expected_message)
|
||||
end
|
||||
|
||||
def description
|
||||
"require #{@attribute} to be set"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blank_value
|
||||
if collection?
|
||||
[]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def collection?
|
||||
if reflection = @subject.class.reflect_on_association(@attribute)
|
||||
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,148 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
# Ensures that the model is invalid if the given attribute is not unique.
|
||||
#
|
||||
# Internally, this uses values from existing records to test validations,
|
||||
# so this will always fail if you have not saved at least one record for
|
||||
# the model being tested, like so:
|
||||
#
|
||||
# describe User do
|
||||
# before(:each) { User.create!(:email => 'address@example.com') }
|
||||
# it { should validate_uniqueness_of(:email) }
|
||||
# end
|
||||
#
|
||||
# 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>: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.
|
||||
#
|
||||
# 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 }
|
||||
#
|
||||
def validate_uniqueness_of(attr)
|
||||
ValidateUniquenessOfMatcher.new(attr)
|
||||
end
|
||||
|
||||
class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc:
|
||||
include Helpers
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute
|
||||
end
|
||||
|
||||
def scoped_to(*scopes)
|
||||
@scopes = [*scopes].flatten
|
||||
self
|
||||
end
|
||||
|
||||
def with_message(message)
|
||||
@expected_message = message
|
||||
self
|
||||
end
|
||||
|
||||
def case_insensitive
|
||||
@case_insensitive = true
|
||||
self
|
||||
end
|
||||
|
||||
def description
|
||||
result = "require "
|
||||
result << "case sensitive " unless @case_insensitive
|
||||
result << "unique value for #{@attribute}"
|
||||
result << " scoped to #{@scopes.join(', ')}" unless @scopes.blank?
|
||||
result
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject.class.new
|
||||
@expected_message ||= :taken
|
||||
find_existing &&
|
||||
set_scoped_attributes &&
|
||||
validate_attribute &&
|
||||
validate_after_scope_change
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_existing
|
||||
if @existing = @subject.class.find(:first)
|
||||
true
|
||||
else
|
||||
@failure_message = "Can't find first #{class_name}"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def set_scoped_attributes
|
||||
unless @scopes.blank?
|
||||
@scopes.each do |scope|
|
||||
setter = :"#{scope}="
|
||||
unless @subject.respond_to?(setter)
|
||||
@failure_message =
|
||||
"#{class_name} doesn't seem to have a #{scope} attribute."
|
||||
return false
|
||||
end
|
||||
@subject.send("#{scope}=", @existing.send(scope))
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def validate_attribute
|
||||
disallows_value_of(existing_value, @expected_message)
|
||||
end
|
||||
|
||||
# TODO: There is a chance that we could change the scoped field
|
||||
# to a value that's already taken. An alternative implementation
|
||||
# could actually find all values for scope and create a unique
|
||||
def validate_after_scope_change
|
||||
if @scopes.blank?
|
||||
true
|
||||
else
|
||||
@scopes.all? do |scope|
|
||||
previous_value = @existing.send(scope)
|
||||
|
||||
# Assume the scope is a foreign key if the field is nil
|
||||
previous_value ||= 0
|
||||
|
||||
next_value = previous_value.next
|
||||
|
||||
@subject.send("#{scope}=", next_value)
|
||||
|
||||
if allows_value_of(existing_value, @expected_message)
|
||||
@negative_failure_message <<
|
||||
" (with different value of #{scope})"
|
||||
true
|
||||
else
|
||||
@failure_message << " (with different value of #{scope})"
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def class_name
|
||||
@subject.class.name
|
||||
end
|
||||
|
||||
def existing_value
|
||||
value = @existing.send(@attribute)
|
||||
value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
56
lib/shoulda/matchers/active_record/validation_matcher.rb
Normal file
56
lib/shoulda/matchers/active_record/validation_matcher.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
module Shoulda # :nodoc:
|
||||
module Matchers
|
||||
module ActiveRecord # :nodoc:
|
||||
|
||||
class ValidationMatcher # :nodoc:
|
||||
|
||||
attr_reader :failure_message
|
||||
|
||||
def initialize(attribute)
|
||||
@attribute = attribute
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
@negative_failure_message || @failure_message
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allows_value_of(value, message = nil)
|
||||
allow = AllowValueMatcher.
|
||||
new(value).
|
||||
for(@attribute).
|
||||
with_message(message)
|
||||
if allow.matches?(@subject)
|
||||
@negative_failure_message = allow.failure_message
|
||||
true
|
||||
else
|
||||
@failure_message = allow.negative_failure_message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def disallows_value_of(value, message = nil)
|
||||
disallow = AllowValueMatcher.
|
||||
new(value).
|
||||
for(@attribute).
|
||||
with_message(message)
|
||||
if disallow.matches?(@subject)
|
||||
@failure_message = disallow.negative_failure_message
|
||||
false
|
||||
else
|
||||
@negative_failure_message = disallow.failure_message
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
23
lib/shoulda/matchers/integrations/rspec.rb
Normal file
23
lib/shoulda/matchers/integrations/rspec.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# :enddoc:
|
||||
|
||||
if defined?(::ActiveRecord)
|
||||
require 'shoulda/matchers/active_record'
|
||||
module RSpec::Matchers
|
||||
include Shoulda::Matchers::ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(::ActionController)
|
||||
require 'shoulda/matchers/action_controller'
|
||||
module RSpec::Rails::ControllerExampleGroup
|
||||
include Shoulda::Matchers::ActionController
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(::ActionMailer)
|
||||
require 'shoulda/matchers/action_mailer'
|
||||
module RSpec::Rails::MailerExampleGroup
|
||||
include Shoulda::Matchers::ActionMailer
|
||||
end
|
||||
end
|
||||
|
41
lib/shoulda/matchers/integrations/test_unit.rb
Normal file
41
lib/shoulda/matchers/integrations/test_unit.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# :enddoc:
|
||||
|
||||
if defined?(ActionController)
|
||||
require 'shoulda/matchers/action_controller'
|
||||
|
||||
class ActionController::TestCase
|
||||
include Shoulda::Matchers::ActionController
|
||||
extend Shoulda::Matchers::ActionController
|
||||
|
||||
def subject
|
||||
@controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActionMailer)
|
||||
require 'shoulda/matchers/action_mailer'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::Matchers::ActionMailer
|
||||
extend Shoulda::Matchers::ActionMailer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
require 'shoulda/matchers/active_record'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class TestCase
|
||||
include Shoulda::Matchers::ActiveRecord
|
||||
extend Shoulda::Matchers::ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
6
lib/shoulda/matchers/version.rb
Normal file
6
lib/shoulda/matchers/version.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
VERSION = "2.11.3"
|
||||
end
|
||||
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
module Shoulda
|
||||
VERSION = "2.11.3"
|
||||
end
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
||||
require 'shoulda/version'
|
||||
require 'shoulda/matchers/version'
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{shoulda}
|
||||
s.version = Shoulda::VERSION
|
||||
s.name = %q{shoulda-matchers}
|
||||
s.version = Shoulda::Matchers::VERSION
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["Tammer Saleh", "Joe Ferris", "Ryan McGeary", "Dan Croak",
|
||||
|
@ -15,7 +15,6 @@ Gem::Specification.new do |s|
|
|||
s.homepage = %q{http://thoughtbot.com/community/}
|
||||
s.rdoc_options = ["--line-numbers", "--main", "README.rdoc"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubyforge_project = %q{shoulda}
|
||||
s.rubygems_version = %q{1.3.5}
|
||||
s.summary = %q{Making tests easy on the fingers and eyes}
|
||||
s.description = %q{Making tests easy on the fingers and eyes}
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::AssignToMatcher do
|
||||
describe Shoulda::Matchers::ActionController::AssignToMatcher do
|
||||
|
||||
context "a controller that assigns to an instance variable" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::FilterParamMatcher do
|
||||
describe Shoulda::Matchers::ActionController::FilterParamMatcher do
|
||||
|
||||
context "given parameter filters" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::RedirectToMatcher do
|
||||
describe Shoulda::Matchers::ActionController::RedirectToMatcher do
|
||||
|
||||
context "a controller that redirects" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::RenderTemplateMatcher do
|
||||
describe Shoulda::Matchers::ActionController::RenderTemplateMatcher do
|
||||
include ActionController::TemplateAssertions
|
||||
|
||||
context "a controller that renders a template" do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::RenderWithLayoutMatcher do
|
||||
describe Shoulda::Matchers::ActionController::RenderWithLayoutMatcher do
|
||||
include ActionController::TemplateAssertions
|
||||
|
||||
context "a controller that renders with a layout" do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::RespondWithContentTypeMatcher do
|
||||
describe Shoulda::Matchers::ActionController::RespondWithContentTypeMatcher do
|
||||
|
||||
context "a controller responding with content type :xml" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::RespondWithMatcher do
|
||||
describe Shoulda::Matchers::ActionController::RespondWithMatcher do
|
||||
|
||||
context "a controller responding with success" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::RouteMatcher do
|
||||
describe Shoulda::Matchers::ActionController::RouteMatcher do
|
||||
|
||||
context "given a controller with a defined glob url" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::SetSessionMatcher do
|
||||
describe Shoulda::Matchers::ActionController::SetSessionMatcher do
|
||||
|
||||
context "a controller that sets a session variable" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionController::SetTheFlashMatcher do
|
||||
describe Shoulda::Matchers::ActionController::SetTheFlashMatcher do
|
||||
|
||||
context "a controller that sets a flash message" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActionMailer::HaveSentEmailMatcher do
|
||||
describe Shoulda::Matchers::ActionMailer::HaveSentEmailMatcher do
|
||||
def add_mail_to_deliveries
|
||||
::ActionMailer::Base.deliveries << Mailer.the_email
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::AllowMassAssignmentOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::AllowMassAssignmentOfMatcher do
|
||||
|
||||
context "an attribute that is blacklisted from mass-assignment" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::AllowValueMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::AllowValueMatcher do
|
||||
|
||||
context "an attribute with a format validation" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::AssociationMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
||||
|
||||
context "belong_to" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::EnsureInclusionOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::EnsureInclusionOfMatcher do
|
||||
|
||||
context "an attribute which must be included in a range" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::EnsureLengthOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::EnsureLengthOfMatcher do
|
||||
|
||||
context "an attribute with a non-zero minimum length validation" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::HaveDbColumnMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::HaveDbColumnMatcher do
|
||||
|
||||
context "have_db_column" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::HaveDbIndexMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::HaveDbIndexMatcher do
|
||||
|
||||
context "have_db_index" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::HaveReadonlyAttributeMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::HaveReadonlyAttributeMatcher do
|
||||
|
||||
context "an attribute that cannot be set after being saved" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::ValidateAcceptanceOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::ValidateAcceptanceOfMatcher do
|
||||
|
||||
context "an attribute which must be accepted" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::ValidateFormatOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::ValidateFormatOfMatcher do
|
||||
|
||||
context "a postal code" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::ValidateNumericalityOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::ValidateNumericalityOfMatcher do
|
||||
|
||||
context "a numeric attribute" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::ValidatePresenceOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::ValidatePresenceOfMatcher do
|
||||
|
||||
context "a required attribute" do
|
||||
before do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Shoulda::ActiveRecord::ValidateUniquenessOfMatcher do
|
||||
describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher do
|
||||
|
||||
context "a unique attribute" do
|
||||
before do
|
||||
|
|
|
@ -15,7 +15,7 @@ $LOAD_PATH << File.join(PROJECT_ROOT, 'lib')
|
|||
|
||||
Dir[File.join(PROJECT_ROOT, 'spec', 'support', '**', '*.rb')].each { |file| require(file) }
|
||||
|
||||
require 'shoulda'
|
||||
require 'shoulda-matchers'
|
||||
require 'rspec/rails'
|
||||
|
||||
# Run the migrations
|
||||
|
@ -24,9 +24,9 @@ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
|
|||
|
||||
RSpec.configure do |config|
|
||||
config.mock_with :mocha
|
||||
config.include Shoulda::ActionController,
|
||||
config.include Shoulda::Matchers::ActionController,
|
||||
:example_group => { :file_path => /action_controller/ }
|
||||
config.include Shoulda::ActionMailer,
|
||||
config.include Shoulda::Matchers::ActionMailer,
|
||||
:example_group => { :file_path => /action_mailer/ }
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue