Removed ThoughtBot module.
The ThoughtBot module had been added (by me) as a paranoid namespacing measure, just in case any other code wanted to use a class or module named Shoulda. Silly.
This commit is contained in:
parent
5e4222694b
commit
fc938bb185
|
@ -10,7 +10,7 @@ Assertions:: Many common rails testing idioms have been distilled into a set of
|
|||
|
||||
= Usage
|
||||
|
||||
=== Context Helpers (ThoughtBot::Shoulda::Context)
|
||||
=== Context Helpers (Shoulda::Context)
|
||||
|
||||
Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
|
||||
|
||||
|
@ -43,7 +43,7 @@ Produces the following test methods:
|
|||
|
||||
So readable!
|
||||
|
||||
=== ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord)
|
||||
=== ActiveRecord Tests (Shoulda::ActiveRecord)
|
||||
|
||||
Quick macro tests for your ActiveRecord associations and validations:
|
||||
|
||||
|
@ -73,7 +73,7 @@ Quick macro tests for your ActiveRecord associations and validations:
|
|||
|
||||
Makes TDD so much easier.
|
||||
|
||||
=== Controller Tests (ThoughtBot::Shoulda::Controller::ClassMethods)
|
||||
=== Controller Tests (Shoulda::Controller::ClassMethods)
|
||||
|
||||
Macros to test the most common controller patterns...
|
||||
|
||||
|
@ -105,7 +105,7 @@ Test entire controllers in a few lines...
|
|||
|
||||
should_be_restful generates 40 tests on the fly, for both html and xml requests.
|
||||
|
||||
=== Helpful Assertions (ThoughtBot::Shoulda::General)
|
||||
=== Helpful Assertions (Shoulda::General)
|
||||
|
||||
More to come here, but have fun with what's there.
|
||||
|
||||
|
|
|
@ -26,10 +26,10 @@ module Test # :nodoc: all
|
|||
module Unit
|
||||
class TestCase
|
||||
|
||||
include ThoughtBot::Shoulda::General
|
||||
include ThoughtBot::Shoulda::Controller
|
||||
include Shoulda::General
|
||||
include Shoulda::Controller
|
||||
|
||||
extend ThoughtBot::Shoulda::ActiveRecord
|
||||
extend Shoulda::ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -37,7 +37,7 @@ end
|
|||
module ActionController #:nodoc: all
|
||||
module Integration
|
||||
class Session
|
||||
include ThoughtBot::Shoulda::General
|
||||
include Shoulda::General
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,7 @@ require 'test/unit/ui/console/testrunner'
|
|||
# every rake task, as though there was another (empty) set of tests.
|
||||
# A fix would be most welcome.
|
||||
#
|
||||
module ThoughtBot::Shoulda::Color
|
||||
module Shoulda::Color
|
||||
COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 } # :nodoc:
|
||||
def self.method_missing(color_name, *args) # :nodoc:
|
||||
color(color_name) + args.first + color(:clear)
|
||||
|
@ -25,7 +25,7 @@ module Test # :nodoc:
|
|||
alias :old_to_s :to_s
|
||||
def to_s
|
||||
if old_to_s =~ /\d+ tests, \d+ assertions, (\d+) failures, (\d+) errors/
|
||||
ThoughtBot::Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
|
||||
Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -43,16 +43,16 @@ module Test # :nodoc:
|
|||
class Failure # :nodoc:
|
||||
alias :old_long_display :long_display
|
||||
def long_display
|
||||
# old_long_display.sub('Failure', ThoughtBot::Shoulda::Color.red('Failure'))
|
||||
ThoughtBot::Shoulda::Color.red(old_long_display)
|
||||
# old_long_display.sub('Failure', Shoulda::Color.red('Failure'))
|
||||
Shoulda::Color.red(old_long_display)
|
||||
end
|
||||
end
|
||||
|
||||
class Error # :nodoc:
|
||||
alias :old_long_display :long_display
|
||||
def long_display
|
||||
# old_long_display.sub('Error', ThoughtBot::Shoulda::Color.yellow('Error'))
|
||||
ThoughtBot::Shoulda::Color.yellow(old_long_display)
|
||||
# old_long_display.sub('Error', Shoulda::Color.yellow('Error'))
|
||||
Shoulda::Color.yellow(old_long_display)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -62,9 +62,9 @@ module Test # :nodoc:
|
|||
def output_single(something, level=NORMAL)
|
||||
return unless (output?(level))
|
||||
something = case something
|
||||
when '.' then ThoughtBot::Shoulda::Color.green('.')
|
||||
when 'F' then ThoughtBot::Shoulda::Color.red("F")
|
||||
when 'E' then ThoughtBot::Shoulda::Color.yellow("E")
|
||||
when '.' then Shoulda::Color.green('.')
|
||||
when 'F' then Shoulda::Color.red("F")
|
||||
when 'E' then Shoulda::Color.yellow("E")
|
||||
else something
|
||||
end
|
||||
@io.write(something)
|
||||
|
|
|
@ -1,467 +1,464 @@
|
|||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Controller
|
||||
def self.included(other) # :nodoc:
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::Controller::ClassMethods
|
||||
include ThoughtBot::Shoulda::Controller::InstanceMethods
|
||||
ThoughtBot::Shoulda::Controller::ClassMethods::VALID_FORMATS.each do |format|
|
||||
include "ThoughtBot::Shoulda::Controller::#{format.to_s.upcase}".constantize
|
||||
end
|
||||
module Shoulda # :nodoc:
|
||||
module Controller
|
||||
def self.included(other) # :nodoc:
|
||||
other.class_eval do
|
||||
extend Shoulda::Controller::ClassMethods
|
||||
include Shoulda::Controller::InstanceMethods
|
||||
Shoulda::Controller::ClassMethods::VALID_FORMATS.each do |format|
|
||||
include "Shoulda::Controller::#{format.to_s.upcase}".constantize
|
||||
end
|
||||
end
|
||||
|
||||
# = Macro test helpers for your controllers
|
||||
#
|
||||
# By using the macro helpers you can quickly and easily create concise and easy to read test suites.
|
||||
end
|
||||
|
||||
# = Macro test helpers for your controllers
|
||||
#
|
||||
# By using the macro helpers you can quickly and easily create concise and easy to read test suites.
|
||||
#
|
||||
# This code segment:
|
||||
# context "on GET to :show for first record" do
|
||||
# setup do
|
||||
# get :show, :id => 1
|
||||
# end
|
||||
#
|
||||
# should_assign_to :user
|
||||
# should_respond_with :success
|
||||
# should_render_template :show
|
||||
# should_not_set_the_flash
|
||||
#
|
||||
# should "do something else really cool" do
|
||||
# assert_equal 1, assigns(:user).id
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the +show+ action
|
||||
#
|
||||
# Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your
|
||||
# controller responds restfully to a variety of requested formats.
|
||||
module ClassMethods
|
||||
# Formats tested by #should_be_restful. Defaults to [:html, :xml]
|
||||
VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
|
||||
VALID_FORMATS.each {|f| require "shoulda/controller_tests/formats/#{f}.rb"}
|
||||
|
||||
# Actions tested by #should_be_restful
|
||||
VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
|
||||
|
||||
# A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
|
||||
#
|
||||
# This code segment:
|
||||
# context "on GET to :show for first record" do
|
||||
# setup do
|
||||
# get :show, :id => 1
|
||||
# Example:
|
||||
# class UsersControllerTest < Test::Unit::TestCase
|
||||
# load_all_fixtures
|
||||
#
|
||||
# def setup
|
||||
# ...normal setup code...
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should_assign_to :user
|
||||
# should_respond_with :success
|
||||
# should_render_template :show
|
||||
# should_not_set_the_flash
|
||||
#
|
||||
# should "do something else really cool" do
|
||||
# assert_equal 1, assigns(:user).id
|
||||
# should_be_restful do |resource|
|
||||
# resource.identifier = :id
|
||||
# resource.klass = User
|
||||
# resource.object = :user
|
||||
# resource.parent = []
|
||||
# resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
|
||||
# resource.formats = [:html, :xml]
|
||||
#
|
||||
# resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
|
||||
# resource.update.params = { :name => "sue" }
|
||||
#
|
||||
# resource.create.redirect = "user_url(@user)"
|
||||
# resource.update.redirect = "user_url(@user)"
|
||||
# resource.destroy.redirect = "users_url"
|
||||
#
|
||||
# resource.create.flash = /created/i
|
||||
# resource.update.flash = /updated/i
|
||||
# resource.destroy.flash = /removed/i
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the +show+ action
|
||||
# Whenever possible, the resource attributes will be set to sensible defaults.
|
||||
#
|
||||
# Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your
|
||||
# controller responds restfully to a variety of requested formats.
|
||||
module ClassMethods
|
||||
# Formats tested by #should_be_restful. Defaults to [:html, :xml]
|
||||
VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
|
||||
VALID_FORMATS.each {|f| require "shoulda/controller_tests/formats/#{f}.rb"}
|
||||
class ResourceOptions
|
||||
# Configuration options for the create, update, destroy actions under should_be_restful
|
||||
class ActionOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
#
|
||||
# Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
|
||||
attr_accessor :redirect
|
||||
|
||||
# Actions tested by #should_be_restful
|
||||
VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Defaults:
|
||||
# destroy:: /removed/
|
||||
# create:: /created/
|
||||
# update:: /updated/
|
||||
attr_accessor :flash
|
||||
|
||||
# Hash describing the params that should be sent in with this action.
|
||||
attr_accessor :params
|
||||
end
|
||||
|
||||
# A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
|
||||
#
|
||||
# Configuration options for the denied actions under should_be_restful
|
||||
#
|
||||
# Example:
|
||||
# class UsersControllerTest < Test::Unit::TestCase
|
||||
# load_all_fixtures
|
||||
#
|
||||
# def setup
|
||||
# ...normal setup code...
|
||||
# @user = User.find(:first)
|
||||
# context "The public" do
|
||||
# setup do
|
||||
# @request.session[:logged_in] = false
|
||||
# end
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.identifier = :id
|
||||
# resource.klass = User
|
||||
# resource.object = :user
|
||||
# resource.parent = []
|
||||
# resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
|
||||
# resource.formats = [:html, :xml]
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
|
||||
# resource.update.params = { :name => "sue" }
|
||||
#
|
||||
# resource.create.redirect = "user_url(@user)"
|
||||
# resource.update.redirect = "user_url(@user)"
|
||||
# resource.destroy.redirect = "users_url"
|
||||
#
|
||||
# resource.create.flash = /created/i
|
||||
# resource.update.flash = /updated/i
|
||||
# resource.destroy.flash = /removed/i
|
||||
# resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
|
||||
# resource.denied.flash = /get outta here/i
|
||||
# resource.denied.redirect = 'new_session_url'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class DeniedOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.flash = /created/
|
||||
attr_accessor :flash
|
||||
|
||||
# Actions that should be denied (only used by resource.denied). <i>Note that these actions will
|
||||
# only be tested if they are also listed in +resource.actions+</i>
|
||||
# The special value of :all will deny all of the REST actions.
|
||||
attr_accessor :actions
|
||||
end
|
||||
|
||||
# Name of key in params that references the primary key.
|
||||
# Will almost always be :id (default), unless you are using a plugin or have patched rails.
|
||||
attr_accessor :identifier
|
||||
|
||||
# Name of the ActiveRecord class this resource is responsible for. Automatically determined from
|
||||
# test class if not explicitly set. UserTest => "User"
|
||||
attr_accessor :klass
|
||||
|
||||
# Name of the instantiated ActiveRecord object that should be used by some of the tests.
|
||||
# Defaults to the underscored name of the AR class. CompanyManager => :company_manager
|
||||
attr_accessor :object
|
||||
|
||||
# Name of the parent AR objects. Can be set as parent= or parents=, and can take either
|
||||
# the name of the parent resource (if there's only one), or an array of names (if there's
|
||||
# more than one).
|
||||
#
|
||||
# Example:
|
||||
# # in the routes...
|
||||
# map.resources :companies do
|
||||
# map.resources :people do
|
||||
# map.resources :limbs
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Whenever possible, the resource attributes will be set to sensible defaults.
|
||||
#
|
||||
class ResourceOptions
|
||||
# Configuration options for the create, update, destroy actions under should_be_restful
|
||||
class ActionOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
#
|
||||
# Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Defaults:
|
||||
# destroy:: /removed/
|
||||
# create:: /created/
|
||||
# update:: /updated/
|
||||
attr_accessor :flash
|
||||
|
||||
# Hash describing the params that should be sent in with this action.
|
||||
attr_accessor :params
|
||||
end
|
||||
|
||||
# Configuration options for the denied actions under should_be_restful
|
||||
#
|
||||
# Example:
|
||||
# context "The public" do
|
||||
# setup do
|
||||
# @request.session[:logged_in] = false
|
||||
# end
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
|
||||
# resource.denied.flash = /get outta here/i
|
||||
# resource.denied.redirect = 'new_session_url'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class DeniedOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.flash = /created/
|
||||
attr_accessor :flash
|
||||
|
||||
# Actions that should be denied (only used by resource.denied). <i>Note that these actions will
|
||||
# only be tested if they are also listed in +resource.actions+</i>
|
||||
# The special value of :all will deny all of the REST actions.
|
||||
attr_accessor :actions
|
||||
end
|
||||
|
||||
# Name of key in params that references the primary key.
|
||||
# Will almost always be :id (default), unless you are using a plugin or have patched rails.
|
||||
attr_accessor :identifier
|
||||
|
||||
# Name of the ActiveRecord class this resource is responsible for. Automatically determined from
|
||||
# test class if not explicitly set. UserTest => "User"
|
||||
attr_accessor :klass
|
||||
|
||||
# Name of the instantiated ActiveRecord object that should be used by some of the tests.
|
||||
# Defaults to the underscored name of the AR class. CompanyManager => :company_manager
|
||||
attr_accessor :object
|
||||
|
||||
# Name of the parent AR objects. Can be set as parent= or parents=, and can take either
|
||||
# the name of the parent resource (if there's only one), or an array of names (if there's
|
||||
# more than one).
|
||||
#
|
||||
# Example:
|
||||
# # in the routes...
|
||||
# map.resources :companies do
|
||||
# map.resources :people do
|
||||
# map.resources :limbs
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # in the tests...
|
||||
# class PeopleControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :companies
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class LimbsControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parents = [:companies, :people]
|
||||
# end
|
||||
# end
|
||||
attr_accessor :parent
|
||||
alias parents parent
|
||||
alias parents= parent=
|
||||
|
||||
# Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
|
||||
# Tests for each actionw will only be generated if the action is listed here.
|
||||
# The special value of :all will test all of the REST actions.
|
||||
#
|
||||
# Example (for a read-only controller):
|
||||
# resource.actions = [:show, :index]
|
||||
attr_accessor :actions
|
||||
|
||||
# Formats that should be tested. Must be a subset of VALID_FORMATS (default).
|
||||
# Each action will be tested against the formats listed here. The special value
|
||||
# of :all will test all of the supported formats.
|
||||
#
|
||||
# Example:
|
||||
# resource.actions = [:html, :xml]
|
||||
attr_accessor :formats
|
||||
|
||||
# ActionOptions object specifying options for the create action.
|
||||
attr_accessor :create
|
||||
|
||||
# ActionOptions object specifying options for the update action.
|
||||
attr_accessor :update
|
||||
|
||||
# ActionOptions object specifying options for the desrtoy action.
|
||||
attr_accessor :destroy
|
||||
|
||||
# DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
|
||||
attr_accessor :denied
|
||||
|
||||
def initialize # :nodoc:
|
||||
@create = ActionOptions.new
|
||||
@update = ActionOptions.new
|
||||
@destroy = ActionOptions.new
|
||||
@denied = DeniedOptions.new
|
||||
|
||||
@create.flash ||= /created/i
|
||||
@update.flash ||= /updated/i
|
||||
@destroy.flash ||= /removed/i
|
||||
@denied.flash ||= /denied/i
|
||||
|
||||
@create.params ||= {}
|
||||
@update.params ||= {}
|
||||
|
||||
@actions = VALID_ACTIONS
|
||||
@formats = VALID_FORMATS
|
||||
@denied.actions = []
|
||||
end
|
||||
|
||||
def normalize!(target) # :nodoc:
|
||||
@denied.actions = VALID_ACTIONS if @denied.actions == :all
|
||||
@actions = VALID_ACTIONS if @actions == :all
|
||||
@formats = VALID_FORMATS if @formats == :all
|
||||
|
||||
@denied.actions = @denied.actions.map(&:to_sym)
|
||||
@actions = @actions.map(&:to_sym)
|
||||
@formats = @formats.map(&:to_sym)
|
||||
|
||||
ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
|
||||
ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
|
||||
ensure_valid_members(@formats, VALID_FORMATS, 'formats')
|
||||
|
||||
@identifier ||= :id
|
||||
@klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
|
||||
@object ||= @klass.name.tableize.singularize
|
||||
@parent ||= []
|
||||
@parent = [@parent] unless @parent.is_a? Array
|
||||
|
||||
collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
|
||||
collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
|
||||
@destroy.redirect ||= "#{collection_helper}(#{collection_args})"
|
||||
|
||||
member_helper = [@parent, @object, 'url'].flatten.join('_')
|
||||
member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
|
||||
@create.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@update.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@denied.redirect ||= "new_session_url"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_valid_members(ary, valid_members, name) # :nodoc:
|
||||
invalid = ary - valid_members
|
||||
raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# :section: should_be_restful
|
||||
# Generates a full suite of tests for a restful controller.
|
||||
#
|
||||
# The following definition will generate tests for the +index+, +show+, +new+,
|
||||
# +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats:
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.create.params = { :title => "first post", :body => 'blah blah blah'}
|
||||
# resource.update.params = { :title => "changed" }
|
||||
# # in the tests...
|
||||
# class PeopleControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :companies
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This generates about 40 tests, all of the format:
|
||||
# "on GET to :show should assign @user."
|
||||
# "on GET to :show should not set the flash."
|
||||
# "on GET to :show should render 'show' template."
|
||||
# "on GET to :show should respond with success."
|
||||
# "on GET to :show as xml should assign @user."
|
||||
# "on GET to :show as xml should have ContentType set to 'application/xml'."
|
||||
# "on GET to :show as xml should respond with success."
|
||||
# "on GET to :show as xml should return <user/> as the root element."
|
||||
# The +resource+ parameter passed into the block is a ResourceOptions object, and
|
||||
# is used to configure the tests for the details of your resources.
|
||||
# class LimbsControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parents = [:companies, :people]
|
||||
# end
|
||||
# end
|
||||
attr_accessor :parent
|
||||
alias parents parent
|
||||
alias parents= parent=
|
||||
|
||||
# Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
|
||||
# Tests for each actionw will only be generated if the action is listed here.
|
||||
# The special value of :all will test all of the REST actions.
|
||||
#
|
||||
def should_be_restful(&blk) # :yields: resource
|
||||
resource = ResourceOptions.new
|
||||
blk.call(resource)
|
||||
resource.normalize!(self)
|
||||
# Example (for a read-only controller):
|
||||
# resource.actions = [:show, :index]
|
||||
attr_accessor :actions
|
||||
|
||||
resource.formats.each do |format|
|
||||
resource.actions.each do |action|
|
||||
if self.respond_to? :"make_#{action}_#{format}_tests"
|
||||
self.send(:"make_#{action}_#{format}_tests", resource)
|
||||
else
|
||||
should "test #{action} #{format}" do
|
||||
flunk "Test for #{action} as #{format} not implemented"
|
||||
end
|
||||
# Formats that should be tested. Must be a subset of VALID_FORMATS (default).
|
||||
# Each action will be tested against the formats listed here. The special value
|
||||
# of :all will test all of the supported formats.
|
||||
#
|
||||
# Example:
|
||||
# resource.actions = [:html, :xml]
|
||||
attr_accessor :formats
|
||||
|
||||
# ActionOptions object specifying options for the create action.
|
||||
attr_accessor :create
|
||||
|
||||
# ActionOptions object specifying options for the update action.
|
||||
attr_accessor :update
|
||||
|
||||
# ActionOptions object specifying options for the desrtoy action.
|
||||
attr_accessor :destroy
|
||||
|
||||
# DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
|
||||
attr_accessor :denied
|
||||
|
||||
def initialize # :nodoc:
|
||||
@create = ActionOptions.new
|
||||
@update = ActionOptions.new
|
||||
@destroy = ActionOptions.new
|
||||
@denied = DeniedOptions.new
|
||||
|
||||
@create.flash ||= /created/i
|
||||
@update.flash ||= /updated/i
|
||||
@destroy.flash ||= /removed/i
|
||||
@denied.flash ||= /denied/i
|
||||
|
||||
@create.params ||= {}
|
||||
@update.params ||= {}
|
||||
|
||||
@actions = VALID_ACTIONS
|
||||
@formats = VALID_FORMATS
|
||||
@denied.actions = []
|
||||
end
|
||||
|
||||
def normalize!(target) # :nodoc:
|
||||
@denied.actions = VALID_ACTIONS if @denied.actions == :all
|
||||
@actions = VALID_ACTIONS if @actions == :all
|
||||
@formats = VALID_FORMATS if @formats == :all
|
||||
|
||||
@denied.actions = @denied.actions.map(&:to_sym)
|
||||
@actions = @actions.map(&:to_sym)
|
||||
@formats = @formats.map(&:to_sym)
|
||||
|
||||
ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
|
||||
ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
|
||||
ensure_valid_members(@formats, VALID_FORMATS, 'formats')
|
||||
|
||||
@identifier ||= :id
|
||||
@klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
|
||||
@object ||= @klass.name.tableize.singularize
|
||||
@parent ||= []
|
||||
@parent = [@parent] unless @parent.is_a? Array
|
||||
|
||||
collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
|
||||
collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
|
||||
@destroy.redirect ||= "#{collection_helper}(#{collection_args})"
|
||||
|
||||
member_helper = [@parent, @object, 'url'].flatten.join('_')
|
||||
member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
|
||||
@create.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@update.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@denied.redirect ||= "new_session_url"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_valid_members(ary, valid_members, name) # :nodoc:
|
||||
invalid = ary - valid_members
|
||||
raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# :section: should_be_restful
|
||||
# Generates a full suite of tests for a restful controller.
|
||||
#
|
||||
# The following definition will generate tests for the +index+, +show+, +new+,
|
||||
# +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats:
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.create.params = { :title => "first post", :body => 'blah blah blah'}
|
||||
# resource.update.params = { :title => "changed" }
|
||||
# end
|
||||
#
|
||||
# This generates about 40 tests, all of the format:
|
||||
# "on GET to :show should assign @user."
|
||||
# "on GET to :show should not set the flash."
|
||||
# "on GET to :show should render 'show' template."
|
||||
# "on GET to :show should respond with success."
|
||||
# "on GET to :show as xml should assign @user."
|
||||
# "on GET to :show as xml should have ContentType set to 'application/xml'."
|
||||
# "on GET to :show as xml should respond with success."
|
||||
# "on GET to :show as xml should return <user/> as the root element."
|
||||
# The +resource+ parameter passed into the block is a ResourceOptions object, and
|
||||
# is used to configure the tests for the details of your resources.
|
||||
#
|
||||
def should_be_restful(&blk) # :yields: resource
|
||||
resource = ResourceOptions.new
|
||||
blk.call(resource)
|
||||
resource.normalize!(self)
|
||||
|
||||
resource.formats.each do |format|
|
||||
resource.actions.each do |action|
|
||||
if self.respond_to? :"make_#{action}_#{format}_tests"
|
||||
self.send(:"make_#{action}_#{format}_tests", resource)
|
||||
else
|
||||
should "test #{action} #{format}" do
|
||||
flunk "Test for #{action} as #{format} not implemented"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# :section: Test macros
|
||||
|
||||
# Macro that creates a test asserting that the flash contains the given value.
|
||||
# val can be a String, a Regex, or nil (indicating that the flash should not be set)
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_set_the_flash_to "Thank you for placing this order."
|
||||
# should_set_the_flash_to /created/i
|
||||
# should_set_the_flash_to nil
|
||||
def should_set_the_flash_to(val)
|
||||
if val
|
||||
should "have #{val.inspect} in the flash" do
|
||||
assert_contains flash.values, val, ", Flash: #{flash.inspect}"
|
||||
end
|
||||
else
|
||||
should "not set the flash" do
|
||||
assert_equal({}, flash, "Flash was set to:\n#{flash.inspect}")
|
||||
end
|
||||
# :section: Test macros
|
||||
|
||||
# Macro that creates a test asserting that the flash contains the given value.
|
||||
# val can be a String, a Regex, or nil (indicating that the flash should not be set)
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_set_the_flash_to "Thank you for placing this order."
|
||||
# should_set_the_flash_to /created/i
|
||||
# should_set_the_flash_to nil
|
||||
def should_set_the_flash_to(val)
|
||||
if val
|
||||
should "have #{val.inspect} in the flash" do
|
||||
assert_contains flash.values, val, ", Flash: #{flash.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the flash is empty. Same as
|
||||
# @should_set_the_flash_to nil@
|
||||
def should_not_set_the_flash
|
||||
should_set_the_flash_to nil
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller assigned to @name
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_assign_to :user
|
||||
def should_assign_to(name)
|
||||
should "assign @#{name}" do
|
||||
assert assigns(name.to_sym), "The action isn't assigning to @#{name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller did not assign to @name
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_not_assign_to :user
|
||||
def should_not_assign_to(name)
|
||||
should "not assign to @#{name}" do
|
||||
assert !assigns(name.to_sym), "@#{name} was visible"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller responded with a 'response' status code.
|
||||
# Example:
|
||||
#
|
||||
# should_respond_with :success
|
||||
def should_respond_with(response)
|
||||
should "respond with #{response}" do
|
||||
assert_response response
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller rendered the given template.
|
||||
# Example:
|
||||
#
|
||||
# should_render_template :new
|
||||
def should_render_template(template)
|
||||
should "render template #{template.inspect}" do
|
||||
assert_template template.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller returned a redirect to the given path.
|
||||
# The given string is evaled to produce the resulting redirect path. All of the instance variables
|
||||
# set by the controller are available to the evaled string.
|
||||
# Example:
|
||||
#
|
||||
# should_redirect_to '"/"'
|
||||
# should_redirect_to "users_url(@user)"
|
||||
def should_redirect_to(url)
|
||||
should "redirect to #{url.inspect}" do
|
||||
instantiate_variables_from_assigns do
|
||||
assert_redirected_to eval(url, self.send(:binding), __FILE__, __LINE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the rendered view contains a <form> element.
|
||||
def should_render_a_form
|
||||
should "display a form" do
|
||||
assert_select "form", true, "The template doesn't contain a <form> element"
|
||||
else
|
||||
should "not set the flash" do
|
||||
assert_equal({}, flash, "Flash was set to:\n#{flash.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods # :nodoc:
|
||||
|
||||
private # :enddoc:
|
||||
|
||||
SPECIAL_INSTANCE_VARIABLES = %w{
|
||||
_cookies
|
||||
_flash
|
||||
_headers
|
||||
_params
|
||||
_request
|
||||
_response
|
||||
_session
|
||||
action_name
|
||||
before_filter_chain_aborted
|
||||
cookies
|
||||
flash
|
||||
headers
|
||||
ignore_missing_templates
|
||||
logger
|
||||
params
|
||||
request
|
||||
request_origin
|
||||
response
|
||||
session
|
||||
template
|
||||
template_class
|
||||
template_root
|
||||
url
|
||||
variables_added
|
||||
}.map(&:to_s)
|
||||
|
||||
def instantiate_variables_from_assigns(*names, &blk)
|
||||
old = {}
|
||||
names = (@response.template.assigns.keys - SPECIAL_INSTANCE_VARIABLES) if names.empty?
|
||||
names.each do |name|
|
||||
old[name] = instance_variable_get("@#{name}")
|
||||
instance_variable_set("@#{name}", assigns(name.to_sym))
|
||||
end
|
||||
blk.call
|
||||
names.each do |name|
|
||||
instance_variable_set("@#{name}", old[name])
|
||||
end
|
||||
end
|
||||
|
||||
def get_existing_record(res) # :nodoc:
|
||||
returning(instance_variable_get("@#{res.object}")) do |record|
|
||||
assert(record, "This test requires you to set @#{res.object} in your setup block")
|
||||
end
|
||||
end
|
||||
|
||||
def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc:
|
||||
parent_names ||= resource.parents.reverse
|
||||
return {} if parent_names == [] # Base case
|
||||
parent_name = parent_names.shift
|
||||
parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first)
|
||||
|
||||
{ :"#{parent_name}_id" => parent.to_param }.merge(make_parent_params(resource, parent, parent_names))
|
||||
end
|
||||
|
||||
|
||||
# Macro that creates a test asserting that the flash is empty. Same as
|
||||
# @should_set_the_flash_to nil@
|
||||
def should_not_set_the_flash
|
||||
should_set_the_flash_to nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller assigned to @name
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_assign_to :user
|
||||
def should_assign_to(name)
|
||||
should "assign @#{name}" do
|
||||
assert assigns(name.to_sym), "The action isn't assigning to @#{name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller did not assign to @name
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_not_assign_to :user
|
||||
def should_not_assign_to(name)
|
||||
should "not assign to @#{name}" do
|
||||
assert !assigns(name.to_sym), "@#{name} was visible"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller responded with a 'response' status code.
|
||||
# Example:
|
||||
#
|
||||
# should_respond_with :success
|
||||
def should_respond_with(response)
|
||||
should "respond with #{response}" do
|
||||
assert_response response
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller rendered the given template.
|
||||
# Example:
|
||||
#
|
||||
# should_render_template :new
|
||||
def should_render_template(template)
|
||||
should "render template #{template.inspect}" do
|
||||
assert_template template.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller returned a redirect to the given path.
|
||||
# The given string is evaled to produce the resulting redirect path. All of the instance variables
|
||||
# set by the controller are available to the evaled string.
|
||||
# Example:
|
||||
#
|
||||
# should_redirect_to '"/"'
|
||||
# should_redirect_to "users_url(@user)"
|
||||
def should_redirect_to(url)
|
||||
should "redirect to #{url.inspect}" do
|
||||
instantiate_variables_from_assigns do
|
||||
assert_redirected_to eval(url, self.send(:binding), __FILE__, __LINE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the rendered view contains a <form> element.
|
||||
def should_render_a_form
|
||||
should "display a form" do
|
||||
assert_select "form", true, "The template doesn't contain a <form> element"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods # :nodoc:
|
||||
|
||||
private # :enddoc:
|
||||
|
||||
SPECIAL_INSTANCE_VARIABLES = %w{
|
||||
_cookies
|
||||
_flash
|
||||
_headers
|
||||
_params
|
||||
_request
|
||||
_response
|
||||
_session
|
||||
action_name
|
||||
before_filter_chain_aborted
|
||||
cookies
|
||||
flash
|
||||
headers
|
||||
ignore_missing_templates
|
||||
logger
|
||||
params
|
||||
request
|
||||
request_origin
|
||||
response
|
||||
session
|
||||
template
|
||||
template_class
|
||||
template_root
|
||||
url
|
||||
variables_added
|
||||
}.map(&:to_s)
|
||||
|
||||
def instantiate_variables_from_assigns(*names, &blk)
|
||||
old = {}
|
||||
names = (@response.template.assigns.keys - SPECIAL_INSTANCE_VARIABLES) if names.empty?
|
||||
names.each do |name|
|
||||
old[name] = instance_variable_get("@#{name}")
|
||||
instance_variable_set("@#{name}", assigns(name.to_sym))
|
||||
end
|
||||
blk.call
|
||||
names.each do |name|
|
||||
instance_variable_set("@#{name}", old[name])
|
||||
end
|
||||
end
|
||||
|
||||
def get_existing_record(res) # :nodoc:
|
||||
returning(instance_variable_get("@#{res.object}")) do |record|
|
||||
assert(record, "This test requires you to set @#{res.object} in your setup block")
|
||||
end
|
||||
end
|
||||
|
||||
def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc:
|
||||
parent_names ||= resource.parents.reverse
|
||||
return {} if parent_names == [] # Base case
|
||||
parent_name = parent_names.shift
|
||||
parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first)
|
||||
|
||||
{ :"#{parent_name}_id" => parent.to_param }.merge(make_parent_params(resource, parent, parent_names))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,196 +1,194 @@
|
|||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Controller # :nodoc:
|
||||
module HTML # :nodoc: all
|
||||
def self.included(other)
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::Controller::HTML::ClassMethods
|
||||
module Shoulda # :nodoc:
|
||||
module Controller # :nodoc:
|
||||
module HTML # :nodoc: all
|
||||
def self.included(other)
|
||||
other.class_eval do
|
||||
extend Shoulda::Controller::HTML::ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def controller_name_from_class
|
||||
self.name.gsub(/Test/, '')
|
||||
end
|
||||
|
||||
def make_show_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#show" do
|
||||
setup do
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :show
|
||||
should_not_set_the_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def controller_name_from_class
|
||||
self.name.gsub(/Test/, '')
|
||||
end
|
||||
|
||||
def make_show_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#show" do
|
||||
setup do
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :show
|
||||
should_not_set_the_flash
|
||||
def make_edit_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#edit" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
get :edit, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:edit)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :edit
|
||||
should_not_set_the_flash
|
||||
should_render_a_form
|
||||
should "set @#{res.object} to requested instance" do
|
||||
assert_equal @record, assigns(res.object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#edit" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
get :edit, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
def make_index_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#index" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
should_render_template :index
|
||||
should_not_set_the_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_new_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#new" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:new, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:new)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object
|
||||
should_not_set_the_flash
|
||||
should_render_template :new
|
||||
should_render_a_form
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_html_tests(res)
|
||||
context "on DELETE to #{controller_name_from_class}#destroy" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
|
||||
if res.denied.actions.include?(:edit)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
should "not destroy record" do
|
||||
assert_nothing_raised { assert @record.reload }
|
||||
end
|
||||
else
|
||||
should_set_the_flash_to res.destroy.flash
|
||||
if res.destroy.redirect.is_a? Symbol
|
||||
should_respond_with res.destroy.redirect
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :edit
|
||||
should_not_set_the_flash
|
||||
should_render_a_form
|
||||
should "set @#{res.object} to requested instance" do
|
||||
assert_equal @record, assigns(res.object)
|
||||
should_redirect_to res.destroy.redirect
|
||||
end
|
||||
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
|
||||
@record.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_index_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#index" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
should_render_template :index
|
||||
should_not_set_the_flash
|
||||
end
|
||||
def make_create_html_tests(res)
|
||||
context "on POST to #{controller_name_from_class}#create with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
end
|
||||
|
||||
def make_new_html_tests(res)
|
||||
context "on GET to #{controller_name_from_class}#new" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:new, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:new)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object
|
||||
should_not_set_the_flash
|
||||
should_render_template :new
|
||||
should_render_a_form
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_html_tests(res)
|
||||
context "on DELETE to #{controller_name_from_class}#destroy" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
should_not_assign_to res.object
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
|
||||
should "not destroy record" do
|
||||
assert_nothing_raised { assert @record.reload }
|
||||
end
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to res.create.flash
|
||||
if res.create.redirect.is_a? Symbol
|
||||
should_respond_with res.create.redirect
|
||||
else
|
||||
should_set_the_flash_to res.destroy.flash
|
||||
if res.destroy.redirect.is_a? Symbol
|
||||
should_respond_with res.destroy.redirect
|
||||
else
|
||||
should_redirect_to res.destroy.redirect
|
||||
end
|
||||
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
|
||||
@record.reload
|
||||
end
|
||||
end
|
||||
should_redirect_to res.create.redirect
|
||||
end
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_html_tests(res)
|
||||
context "on POST to #{controller_name_from_class}#create with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to res.create.flash
|
||||
if res.create.redirect.is_a? Symbol
|
||||
should_respond_with res.create.redirect
|
||||
else
|
||||
should_redirect_to res.create.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
def make_update_html_tests(res)
|
||||
context "on PUT to #{controller_name_from_class}#update with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_html_tests(res)
|
||||
context "on PUT to #{controller_name_from_class}#update with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to(res.update.flash)
|
||||
if res.update.redirect.is_a? Symbol
|
||||
should_respond_with res.update.redirect
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to(res.update.flash)
|
||||
if res.update.redirect.is_a? Symbol
|
||||
should_respond_with res.update.redirect
|
||||
else
|
||||
should_redirect_to res.update.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
should_redirect_to res.update.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,170 +1,168 @@
|
|||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Controller # :nodoc:
|
||||
module XML
|
||||
def self.included(other) #:nodoc:
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::Controller::XML::ClassMethods
|
||||
end
|
||||
module Shoulda # :nodoc:
|
||||
module Controller # :nodoc:
|
||||
module XML
|
||||
def self.included(other) #:nodoc:
|
||||
other.class_eval do
|
||||
extend Shoulda::Controller::XML::ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Macro that creates a test asserting that the controller responded with an XML content-type
|
||||
# and that the XML contains +<name/>+ as the root element.
|
||||
def should_respond_with_xml_for(name = nil)
|
||||
should "have ContentType set to 'application/xml'" do
|
||||
assert_xml_response
|
||||
end
|
||||
|
||||
if name
|
||||
should "return <#{name}/> as the root element" do
|
||||
body = @response.body.first(100).map {|l| " #{l}"}
|
||||
assert_select name.to_s.dasherize, 1, "Body:\n#{body}...\nDoes not have <#{name}/> as the root element."
|
||||
end
|
||||
end
|
||||
end
|
||||
alias should_respond_with_xml should_respond_with_xml_for
|
||||
|
||||
protected
|
||||
|
||||
def make_show_xml_tests(res) # :nodoc:
|
||||
context "on GET to #{controller_name_from_class}#show as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need an :edit action
|
||||
end
|
||||
|
||||
def make_new_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need a :new action
|
||||
end
|
||||
|
||||
def make_index_xml_tests(res) # :nodoc:
|
||||
context "on GET to #{controller_name_from_class}#index as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_respond_with 401
|
||||
else
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object.to_s.pluralize
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_xml_tests(res) # :nodoc:
|
||||
context "on DELETE to #{controller_name_from_class}#destroy as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_respond_with 401
|
||||
|
||||
should "not destroy record" do
|
||||
assert @record.reload
|
||||
end
|
||||
else
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
|
||||
@record.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_xml_tests(res) # :nodoc:
|
||||
context "on POST to #{controller_name_from_class}#create as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_respond_with 401
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_xml_tests(res) # :nodoc:
|
||||
context "on PUT to #{controller_name_from_class}#update as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the next request's format to 'application/xml'
|
||||
def request_xml
|
||||
@request.accept = "application/xml"
|
||||
end
|
||||
|
||||
# Asserts that the controller's response was 'application/xml'
|
||||
def assert_xml_response
|
||||
content_type = (@response.headers["Content-Type"] || @response.headers["type"]).to_s
|
||||
regex = %r{\bapplication/xml\b}
|
||||
|
||||
msg = "Content Type '#{content_type.inspect}' doesn't match '#{regex.inspect}'\n"
|
||||
msg += "Body: #{@response.body.first(100).chomp} ..."
|
||||
|
||||
assert_match regex, content_type, msg
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Macro that creates a test asserting that the controller responded with an XML content-type
|
||||
# and that the XML contains +<name/>+ as the root element.
|
||||
def should_respond_with_xml_for(name = nil)
|
||||
should "have ContentType set to 'application/xml'" do
|
||||
assert_xml_response
|
||||
end
|
||||
|
||||
if name
|
||||
should "return <#{name}/> as the root element" do
|
||||
body = @response.body.first(100).map {|l| " #{l}"}
|
||||
assert_select name.to_s.dasherize, 1, "Body:\n#{body}...\nDoes not have <#{name}/> as the root element."
|
||||
end
|
||||
end
|
||||
end
|
||||
alias should_respond_with_xml should_respond_with_xml_for
|
||||
|
||||
protected
|
||||
|
||||
def make_show_xml_tests(res) # :nodoc:
|
||||
context "on GET to #{controller_name_from_class}#show as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need an :edit action
|
||||
end
|
||||
|
||||
def make_new_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need a :new action
|
||||
end
|
||||
|
||||
def make_index_xml_tests(res) # :nodoc:
|
||||
context "on GET to #{controller_name_from_class}#index as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_respond_with 401
|
||||
else
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object.to_s.pluralize
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_xml_tests(res) # :nodoc:
|
||||
context "on DELETE to #{controller_name_from_class}#destroy as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_respond_with 401
|
||||
|
||||
should "not destroy record" do
|
||||
assert @record.reload
|
||||
end
|
||||
else
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
|
||||
@record.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_xml_tests(res) # :nodoc:
|
||||
context "on POST to #{controller_name_from_class}#create as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_respond_with 401
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_xml_tests(res) # :nodoc:
|
||||
context "on PUT to #{controller_name_from_class}#update as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the next request's format to 'application/xml'
|
||||
def request_xml
|
||||
@request.accept = "application/xml"
|
||||
end
|
||||
|
||||
# Asserts that the controller's response was 'application/xml'
|
||||
def assert_xml_response
|
||||
content_type = (@response.headers["Content-Type"] || @response.headers["type"]).to_s
|
||||
regex = %r{\bapplication/xml\b}
|
||||
|
||||
msg = "Content Type '#{content_type.inspect}' doesn't match '#{regex.inspect}'\n"
|
||||
msg += "Body: #{@response.body.first(100).chomp} ..."
|
||||
|
||||
assert_match regex, content_type, msg
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,240 +1,238 @@
|
|||
require File.join(File.dirname(__FILE__), 'proc_extensions')
|
||||
|
||||
module Thoughtbot
|
||||
module Shoulda
|
||||
class << self
|
||||
attr_accessor :current_context
|
||||
end
|
||||
module Shoulda
|
||||
class << self
|
||||
attr_accessor :current_context
|
||||
end
|
||||
|
||||
VERSION = '1.1.1'
|
||||
VERSION = '1.1.1'
|
||||
|
||||
# = Should statements
|
||||
#
|
||||
# Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
|
||||
# contains all the normal code and assertions you're used to seeing, with the added benefit that
|
||||
# they can be wrapped inside context blocks (see below).
|
||||
#
|
||||
# == Example:
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
#
|
||||
# def setup
|
||||
# @user = User.new("John", "Doe")
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
#
|
||||
# end
|
||||
#
|
||||
# ...will produce the following test:
|
||||
# * <tt>"test: User should return its full name. "</tt>
|
||||
#
|
||||
# Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
|
||||
# = Should statements
|
||||
#
|
||||
# Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
|
||||
# contains all the normal code and assertions you're used to seeing, with the added benefit that
|
||||
# they can be wrapped inside context blocks (see below).
|
||||
#
|
||||
# == Example:
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
#
|
||||
# def setup
|
||||
# @user = User.new("John", "Doe")
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
#
|
||||
# end
|
||||
#
|
||||
# ...will produce the following test:
|
||||
# * <tt>"test: User should return its full name. "</tt>
|
||||
#
|
||||
# Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
|
||||
|
||||
def should(name, &blk)
|
||||
should_eventually(name) && return unless block_given?
|
||||
|
||||
if Shoulda.current_context
|
||||
Shoulda.current_context.should(name, &blk)
|
||||
else
|
||||
context_name = self.name.gsub(/Test/, "")
|
||||
context = Thoughtbot::Shoulda::Context.new(context_name, self) do
|
||||
should(name, &blk)
|
||||
end
|
||||
context.build
|
||||
end
|
||||
end
|
||||
|
||||
# Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
|
||||
def should_eventually(name, &blk)
|
||||
def should(name, &blk)
|
||||
should_eventually(name) && return unless block_given?
|
||||
|
||||
if Shoulda.current_context
|
||||
Shoulda.current_context.should(name, &blk)
|
||||
else
|
||||
context_name = self.name.gsub(/Test/, "")
|
||||
context = Thoughtbot::Shoulda::Context.new(context_name, self) do
|
||||
should_eventually(name, &blk)
|
||||
context = Shoulda::Context.new(context_name, self) do
|
||||
should(name, &blk)
|
||||
end
|
||||
context.build
|
||||
end
|
||||
end
|
||||
|
||||
# = Contexts
|
||||
#
|
||||
# A context block groups should statements under a common set of setup/teardown methods.
|
||||
# Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
|
||||
# and readability of your test code.
|
||||
#
|
||||
# A context block can contain setup, should, should_eventually, and teardown blocks.
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# context "A User instance" do
|
||||
# setup do
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This code will produce the method <tt>"test: A User instance should return its full name. "</tt>.
|
||||
#
|
||||
# Contexts may be nested. Nested contexts run their setup blocks from out to in before each
|
||||
# should statement. They then run their teardown blocks from in to out after each should statement.
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# context "A User instance" do
|
||||
# setup do
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
#
|
||||
# context "with a profile" do
|
||||
# setup do
|
||||
# @user.profile = Profile.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return true when sent :has_profile?"
|
||||
# assert @user.has_profile?
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This code will produce the following methods
|
||||
# * <tt>"test: A User instance should return its full name. "</tt>
|
||||
# * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
|
||||
#
|
||||
# <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
|
||||
# tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
|
||||
# Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
|
||||
def should_eventually(name, &blk)
|
||||
context_name = self.name.gsub(/Test/, "")
|
||||
context = Shoulda::Context.new(context_name, self) do
|
||||
should_eventually(name, &blk)
|
||||
end
|
||||
context.build
|
||||
end
|
||||
|
||||
# = Contexts
|
||||
#
|
||||
# A context block groups should statements under a common set of setup/teardown methods.
|
||||
# Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
|
||||
# and readability of your test code.
|
||||
#
|
||||
# A context block can contain setup, should, should_eventually, and teardown blocks.
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# context "A User instance" do
|
||||
# setup do
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This code will produce the method <tt>"test: A User instance should return its full name. "</tt>.
|
||||
#
|
||||
# Contexts may be nested. Nested contexts run their setup blocks from out to in before each
|
||||
# should statement. They then run their teardown blocks from in to out after each should statement.
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# context "A User instance" do
|
||||
# setup do
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
#
|
||||
# context "with a profile" do
|
||||
# setup do
|
||||
# @user.profile = Profile.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return true when sent :has_profile?"
|
||||
# assert @user.has_profile?
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This code will produce the following methods
|
||||
# * <tt>"test: A User instance should return its full name. "</tt>
|
||||
# * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
|
||||
#
|
||||
# <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
|
||||
# tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
|
||||
|
||||
def context(name, &blk)
|
||||
if Shoulda.current_context
|
||||
Shoulda.current_context.context(name, &blk)
|
||||
else
|
||||
context = Shoulda::Context.new(name, self, &blk)
|
||||
context.build
|
||||
end
|
||||
end
|
||||
|
||||
class Context # :nodoc:
|
||||
|
||||
attr_accessor :name # my name
|
||||
attr_accessor :parent # may be another context, or the original test::unit class.
|
||||
attr_accessor :subcontexts # array of contexts nested under myself
|
||||
attr_accessor :setup_block # block given via a setup method
|
||||
attr_accessor :teardown_block # block given via a teardown method
|
||||
attr_accessor :shoulds # array of hashes representing the should statements
|
||||
attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
|
||||
|
||||
def initialize(name, parent, &blk)
|
||||
Shoulda.current_context = self
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.setup_block = nil
|
||||
self.teardown_block = nil
|
||||
self.shoulds = []
|
||||
self.should_eventuallys = []
|
||||
self.subcontexts = []
|
||||
|
||||
blk.bind(self).call
|
||||
Shoulda.current_context = nil
|
||||
end
|
||||
|
||||
def context(name, &blk)
|
||||
if Shoulda.current_context
|
||||
Shoulda.current_context.context(name, &blk)
|
||||
else
|
||||
context = Thoughtbot::Shoulda::Context.new(name, self, &blk)
|
||||
context.build
|
||||
subcontexts << Context.new(name, self, &blk)
|
||||
Shoulda.current_context = self
|
||||
end
|
||||
|
||||
def setup(&blk)
|
||||
self.setup_block = blk
|
||||
end
|
||||
|
||||
def teardown(&blk)
|
||||
self.teardown_block = blk
|
||||
end
|
||||
|
||||
def should(name, &blk)
|
||||
self.shoulds << { :name => name, :block => blk }
|
||||
end
|
||||
|
||||
def should_eventually(name, &blk)
|
||||
self.should_eventuallys << { :name => name, :block => blk }
|
||||
end
|
||||
|
||||
def full_name
|
||||
parent_name = parent.full_name if am_subcontext?
|
||||
return [parent_name, name].join(" ").strip
|
||||
end
|
||||
|
||||
def am_subcontext?
|
||||
parent.is_a?(self.class) # my parent is the same class as myself.
|
||||
end
|
||||
|
||||
def test_unit_class
|
||||
am_subcontext? ? parent.test_unit_class : parent
|
||||
end
|
||||
|
||||
def create_test_from_should_hash(should)
|
||||
test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
|
||||
|
||||
if test_unit_class.instance_methods.include?(test_name.to_s)
|
||||
warn " * WARNING: '#{test_name}' is already defined"
|
||||
end
|
||||
|
||||
context = self
|
||||
test_unit_class.send(:define_method, test_name) do |*args|
|
||||
begin
|
||||
context.run_all_setup_blocks(self)
|
||||
should[:block].bind(self).call
|
||||
ensure
|
||||
context.run_all_teardown_blocks(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Context # :nodoc:
|
||||
|
||||
attr_accessor :name # my name
|
||||
attr_accessor :parent # may be another context, or the original test::unit class.
|
||||
attr_accessor :subcontexts # array of contexts nested under myself
|
||||
attr_accessor :setup_block # block given via a setup method
|
||||
attr_accessor :teardown_block # block given via a teardown method
|
||||
attr_accessor :shoulds # array of hashes representing the should statements
|
||||
attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
|
||||
|
||||
def initialize(name, parent, &blk)
|
||||
Shoulda.current_context = self
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.setup_block = nil
|
||||
self.teardown_block = nil
|
||||
self.shoulds = []
|
||||
self.should_eventuallys = []
|
||||
self.subcontexts = []
|
||||
|
||||
blk.bind(self).call
|
||||
Shoulda.current_context = nil
|
||||
end
|
||||
|
||||
def context(name, &blk)
|
||||
subcontexts << Context.new(name, self, &blk)
|
||||
Shoulda.current_context = self
|
||||
end
|
||||
|
||||
def setup(&blk)
|
||||
self.setup_block = blk
|
||||
end
|
||||
|
||||
def teardown(&blk)
|
||||
self.teardown_block = blk
|
||||
end
|
||||
|
||||
def should(name, &blk)
|
||||
self.shoulds << { :name => name, :block => blk }
|
||||
end
|
||||
|
||||
def should_eventually(name, &blk)
|
||||
self.should_eventuallys << { :name => name, :block => blk }
|
||||
end
|
||||
|
||||
def full_name
|
||||
parent_name = parent.full_name if am_subcontext?
|
||||
return [parent_name, name].join(" ").strip
|
||||
end
|
||||
|
||||
def am_subcontext?
|
||||
parent.is_a?(self.class) # my parent is the same class as myself.
|
||||
end
|
||||
|
||||
def test_unit_class
|
||||
am_subcontext? ? parent.test_unit_class : parent
|
||||
end
|
||||
|
||||
def create_test_from_should_hash(should)
|
||||
test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
|
||||
|
||||
if test_unit_class.instance_methods.include?(test_name.to_s)
|
||||
warn " * WARNING: '#{test_name}' is already defined"
|
||||
end
|
||||
|
||||
context = self
|
||||
test_unit_class.send(:define_method, test_name) do |*args|
|
||||
begin
|
||||
context.run_all_setup_blocks(self)
|
||||
should[:block].bind(self).call
|
||||
ensure
|
||||
context.run_all_teardown_blocks(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_all_setup_blocks(binding)
|
||||
self.parent.run_all_setup_blocks(binding) if am_subcontext?
|
||||
setup_block.bind(binding).call if setup_block
|
||||
end
|
||||
|
||||
def run_all_teardown_blocks(binding)
|
||||
teardown_block.bind(binding).call if teardown_block
|
||||
self.parent.run_all_teardown_blocks(binding) if am_subcontext?
|
||||
end
|
||||
|
||||
def print_should_eventuallys
|
||||
should_eventuallys.each do |should|
|
||||
test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
|
||||
puts " * DEFERRED: " + test_name
|
||||
end
|
||||
subcontexts.each { |context| context.print_should_eventuallys }
|
||||
end
|
||||
|
||||
def build
|
||||
shoulds.each do |should|
|
||||
create_test_from_should_hash(should)
|
||||
end
|
||||
|
||||
subcontexts.each { |context| context.build }
|
||||
|
||||
print_should_eventuallys
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
test_unit_class.send(method, *args, &blk)
|
||||
end
|
||||
|
||||
def run_all_setup_blocks(binding)
|
||||
self.parent.run_all_setup_blocks(binding) if am_subcontext?
|
||||
setup_block.bind(binding).call if setup_block
|
||||
end
|
||||
|
||||
def run_all_teardown_blocks(binding)
|
||||
teardown_block.bind(binding).call if teardown_block
|
||||
self.parent.run_all_teardown_blocks(binding) if am_subcontext?
|
||||
end
|
||||
|
||||
def print_should_eventuallys
|
||||
should_eventuallys.each do |should|
|
||||
test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
|
||||
puts " * DEFERRED: " + test_name
|
||||
end
|
||||
subcontexts.each { |context| context.print_should_eventuallys }
|
||||
end
|
||||
|
||||
def build
|
||||
shoulds.each do |should|
|
||||
create_test_from_should_hash(should)
|
||||
end
|
||||
|
||||
subcontexts.each { |context| context.build }
|
||||
|
||||
print_should_eventuallys
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &blk)
|
||||
test_unit_class.send(method, *args, &blk)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
extend Thoughtbot::Shoulda
|
||||
extend Shoulda
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,118 +1,116 @@
|
|||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module General
|
||||
def self.included(other) # :nodoc:
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::General::ClassMethods
|
||||
end
|
||||
module Shoulda # :nodoc:
|
||||
module General
|
||||
def self.included(other) # :nodoc:
|
||||
other.class_eval do
|
||||
extend Shoulda::General::ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
|
||||
def load_all_fixtures
|
||||
all_fixtures = Dir.glob(File.join(Test::Unit::TestCase.fixture_path, "*.yml")).collect do |f|
|
||||
File.basename(f, '.yml').to_sym
|
||||
end
|
||||
fixtures *all_fixtures
|
||||
end
|
||||
end
|
||||
|
||||
# Prints a message to stdout, tagged with the name of the calling method.
|
||||
def report!(msg = "")
|
||||
puts("#{caller.first}: #{msg}")
|
||||
end
|
||||
|
||||
# Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
|
||||
#
|
||||
# assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
|
||||
def assert_same_elements(a1, a2, msg = nil)
|
||||
[:select, :inject, :size].each do |m|
|
||||
[a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
|
||||
end
|
||||
|
||||
assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
|
||||
assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
|
||||
|
||||
assert_equal(a1h, a2h, msg)
|
||||
end
|
||||
|
||||
# Asserts that the given collection contains item x. If x is a regular expression, ensure that
|
||||
# at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
|
||||
#
|
||||
# assert_contains(['a', '1'], /\d/) => passes
|
||||
# assert_contains(['a', '1'], 'a') => passes
|
||||
# assert_contains(['a', '1'], /not there/) => fails
|
||||
def assert_contains(collection, x, extra_msg = "")
|
||||
collection = [collection] unless collection.is_a?(Array)
|
||||
msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
|
||||
case x
|
||||
when Regexp: assert(collection.detect { |e| e =~ x }, msg)
|
||||
else assert(collection.include?(x), msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
|
||||
# none of the elements from the collection match x.
|
||||
def assert_does_not_contain(collection, x, extra_msg = "")
|
||||
collection = [collection] unless collection.is_a?(Array)
|
||||
msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg
|
||||
case x
|
||||
when Regexp: assert(!collection.detect { |e| e =~ x }, msg)
|
||||
else assert(!collection.include?(x), msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the given object can be saved
|
||||
#
|
||||
# assert_save User.new(params)
|
||||
def assert_save(obj)
|
||||
assert obj.save, "Errors: #{pretty_error_messages obj}"
|
||||
obj.reload
|
||||
end
|
||||
|
||||
# Asserts that the given object is valid
|
||||
#
|
||||
# assert_valid User.new(params)
|
||||
def assert_valid(obj)
|
||||
assert obj.valid?, "Errors: #{pretty_error_messages obj}"
|
||||
end
|
||||
|
||||
# Asserts that an email was delivered. Can take a block that can further
|
||||
# narrow down the types of emails you're expecting.
|
||||
#
|
||||
# assert_sent_email
|
||||
#
|
||||
# Passes if ActionMailer::Base.deliveries has an email
|
||||
#
|
||||
# assert_sent_email do |email|
|
||||
# email.subject =~ /hi there/ && email.to.include?('none@none.com')
|
||||
# end
|
||||
#
|
||||
# Passes if there is an email with subject containing 'hi there' and
|
||||
# 'none@none.com' as one of the recipients.
|
||||
#
|
||||
def assert_sent_email
|
||||
emails = ActionMailer::Base.deliveries
|
||||
assert !emails.empty?, "No emails were sent"
|
||||
if block_given?
|
||||
matching_emails = emails.select {|email| yield email }
|
||||
assert !matching_emails.empty?, "None of the emails matched."
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that no ActionMailer mails were delivered
|
||||
#
|
||||
# assert_did_not_send_email
|
||||
def assert_did_not_send_email
|
||||
msg = "Sent #{ActionMailer::Base.deliveries.size} emails.\n"
|
||||
ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" }
|
||||
assert ActionMailer::Base.deliveries.empty?, msg
|
||||
end
|
||||
|
||||
def pretty_error_messages(obj)
|
||||
obj.errors.map { |a, m| "#{a} #{m} (#{obj.send(a).inspect})" }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
|
||||
def load_all_fixtures
|
||||
all_fixtures = Dir.glob(File.join(Test::Unit::TestCase.fixture_path, "*.yml")).collect do |f|
|
||||
File.basename(f, '.yml').to_sym
|
||||
end
|
||||
fixtures *all_fixtures
|
||||
end
|
||||
end
|
||||
|
||||
# Prints a message to stdout, tagged with the name of the calling method.
|
||||
def report!(msg = "")
|
||||
puts("#{caller.first}: #{msg}")
|
||||
end
|
||||
|
||||
# Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
|
||||
#
|
||||
# assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
|
||||
def assert_same_elements(a1, a2, msg = nil)
|
||||
[:select, :inject, :size].each do |m|
|
||||
[a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
|
||||
end
|
||||
|
||||
assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
|
||||
assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
|
||||
|
||||
assert_equal(a1h, a2h, msg)
|
||||
end
|
||||
|
||||
# Asserts that the given collection contains item x. If x is a regular expression, ensure that
|
||||
# at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
|
||||
#
|
||||
# assert_contains(['a', '1'], /\d/) => passes
|
||||
# assert_contains(['a', '1'], 'a') => passes
|
||||
# assert_contains(['a', '1'], /not there/) => fails
|
||||
def assert_contains(collection, x, extra_msg = "")
|
||||
collection = [collection] unless collection.is_a?(Array)
|
||||
msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
|
||||
case x
|
||||
when Regexp: assert(collection.detect { |e| e =~ x }, msg)
|
||||
else assert(collection.include?(x), msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
|
||||
# none of the elements from the collection match x.
|
||||
def assert_does_not_contain(collection, x, extra_msg = "")
|
||||
collection = [collection] unless collection.is_a?(Array)
|
||||
msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg
|
||||
case x
|
||||
when Regexp: assert(!collection.detect { |e| e =~ x }, msg)
|
||||
else assert(!collection.include?(x), msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the given object can be saved
|
||||
#
|
||||
# assert_save User.new(params)
|
||||
def assert_save(obj)
|
||||
assert obj.save, "Errors: #{pretty_error_messages obj}"
|
||||
obj.reload
|
||||
end
|
||||
|
||||
# Asserts that the given object is valid
|
||||
#
|
||||
# assert_valid User.new(params)
|
||||
def assert_valid(obj)
|
||||
assert obj.valid?, "Errors: #{pretty_error_messages obj}"
|
||||
end
|
||||
|
||||
# Asserts that an email was delivered. Can take a block that can further
|
||||
# narrow down the types of emails you're expecting.
|
||||
#
|
||||
# assert_sent_email
|
||||
#
|
||||
# Passes if ActionMailer::Base.deliveries has an email
|
||||
#
|
||||
# assert_sent_email do |email|
|
||||
# email.subject =~ /hi there/ && email.to.include?('none@none.com')
|
||||
# end
|
||||
#
|
||||
# Passes if there is an email with subject containing 'hi there' and
|
||||
# 'none@none.com' as one of the recipients.
|
||||
#
|
||||
def assert_sent_email
|
||||
emails = ActionMailer::Base.deliveries
|
||||
assert !emails.empty?, "No emails were sent"
|
||||
if block_given?
|
||||
matching_emails = emails.select {|email| yield email }
|
||||
assert !matching_emails.empty?, "None of the emails matched."
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that no ActionMailer mails were delivered
|
||||
#
|
||||
# assert_did_not_send_email
|
||||
def assert_did_not_send_email
|
||||
msg = "Sent #{ActionMailer::Base.deliveries.size} emails.\n"
|
||||
ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" }
|
||||
assert ActionMailer::Base.deliveries.empty?, msg
|
||||
end
|
||||
|
||||
def pretty_error_messages(obj)
|
||||
obj.errors.map { |a, m| "#{a} #{m} (#{obj.send(a).inspect})" }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Private # :nodoc:
|
||||
# Returns the values for the entries in the args hash who's keys are listed in the wanted array.
|
||||
# Will raise if there are keys in the args hash that aren't listed.
|
||||
def get_options!(args, *wanted)
|
||||
ret = []
|
||||
opts = (args.last.is_a?(Hash) ? args.pop : {})
|
||||
wanted.each {|w| ret << opts.delete(w)}
|
||||
raise ArgumentError, "Unsupported options given: #{opts.keys.join(', ')}" unless opts.keys.empty?
|
||||
return *ret
|
||||
end
|
||||
module Shoulda # :nodoc:
|
||||
module Private # :nodoc:
|
||||
# Returns the values for the entries in the args hash who's keys are listed in the wanted array.
|
||||
# Will raise if there are keys in the args hash that aren't listed.
|
||||
def get_options!(args, *wanted)
|
||||
ret = []
|
||||
opts = (args.last.is_a?(Hash) ? args.pop : {})
|
||||
wanted.each {|w| ret << opts.delete(w)}
|
||||
raise ArgumentError, "Unsupported options given: #{opts.keys.join(', ')}" unless opts.keys.empty?
|
||||
return *ret
|
||||
end
|
||||
|
||||
# Returns the model class constant, as determined by the test class name.
|
||||
#
|
||||
# class TestUser; model_class; end => User
|
||||
def model_class
|
||||
self.name.gsub(/Test$/, '').constantize
|
||||
end
|
||||
# Returns the model class constant, as determined by the test class name.
|
||||
#
|
||||
# class TestUser; model_class; end => User
|
||||
def model_class
|
||||
self.name.gsub(/Test$/, '').constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
||||
|
||||
class PrivateHelpersTest < Test::Unit::TestCase # :nodoc:
|
||||
include ThoughtBot::Shoulda::ActiveRecord
|
||||
include Shoulda::ActiveRecord
|
||||
context "get_options!" do
|
||||
should "remove opts from args" do
|
||||
args = [:a, :b, {}]
|
||||
|
|
Loading…
Reference in New Issue