Organizando plugins e gems.

This commit is contained in:
2009-07-16 11:51:47 -03:00
parent 4e22c87074
commit dcfc38eb09
506 changed files with 10538 additions and 45562 deletions

View File

@@ -0,0 +1,258 @@
--- !ruby/object:Gem::Specification
name: thoughtbot-shoulda
version: !ruby/object:Gem::Version
version: 2.10.1
platform: ruby
authors:
- Tammer Saleh
autorequire:
bindir: bin
cert_chain: []
date: 2009-03-05 00:00:00 -03:00
default_executable: convert_to_should_syntax
dependencies: []
description:
email: tsaleh@thoughtbot.com
executables:
- convert_to_should_syntax
extensions: []
extra_rdoc_files:
- README.rdoc
- CONTRIBUTION_GUIDELINES.rdoc
files:
- CONTRIBUTION_GUIDELINES.rdoc
- MIT-LICENSE
- Rakefile
- README.rdoc
- bin/convert_to_should_syntax
- lib/shoulda
- lib/shoulda/action_controller
- lib/shoulda/action_controller/helpers.rb
- lib/shoulda/action_controller/macros.rb
- lib/shoulda/action_controller/matchers
- lib/shoulda/action_controller/matchers/assign_to_matcher.rb
- lib/shoulda/action_controller/matchers/filter_param_matcher.rb
- lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb
- lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb
- lib/shoulda/action_controller/matchers/respond_with_matcher.rb
- lib/shoulda/action_controller/matchers/route_matcher.rb
- lib/shoulda/action_controller/matchers/set_session_matcher.rb
- lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb
- lib/shoulda/action_controller/matchers.rb
- lib/shoulda/action_controller.rb
- lib/shoulda/action_mailer
- lib/shoulda/action_mailer/assertions.rb
- lib/shoulda/action_mailer.rb
- lib/shoulda/action_view
- lib/shoulda/action_view/macros.rb
- lib/shoulda/action_view.rb
- lib/shoulda/active_record
- lib/shoulda/active_record/assertions.rb
- lib/shoulda/active_record/helpers.rb
- lib/shoulda/active_record/macros.rb
- lib/shoulda/active_record/matchers
- lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb
- lib/shoulda/active_record/matchers/allow_value_matcher.rb
- lib/shoulda/active_record/matchers/association_matcher.rb
- lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb
- lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb
- lib/shoulda/active_record/matchers/have_db_column_matcher.rb
- lib/shoulda/active_record/matchers/have_index_matcher.rb
- lib/shoulda/active_record/matchers/have_named_scope_matcher.rb
- lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb
- lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb
- lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb
- lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb
- lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb
- lib/shoulda/active_record/matchers/validation_matcher.rb
- lib/shoulda/active_record/matchers.rb
- lib/shoulda/active_record.rb
- lib/shoulda/assertions.rb
- lib/shoulda/autoload_macros.rb
- lib/shoulda/context.rb
- lib/shoulda/helpers.rb
- lib/shoulda/macros.rb
- lib/shoulda/private_helpers.rb
- lib/shoulda/proc_extensions.rb
- lib/shoulda/rails.rb
- lib/shoulda/rspec.rb
- lib/shoulda/tasks
- lib/shoulda/tasks/list_tests.rake
- lib/shoulda/tasks/yaml_to_shoulda.rake
- lib/shoulda/tasks.rb
- lib/shoulda/test_unit.rb
- lib/shoulda.rb
- rails/init.rb
- test/fail_macros.rb
- test/fixtures
- test/fixtures/addresses.yml
- test/fixtures/friendships.yml
- test/fixtures/posts.yml
- test/fixtures/products.yml
- test/fixtures/taggings.yml
- test/fixtures/tags.yml
- test/fixtures/users.yml
- test/functional
- test/functional/posts_controller_test.rb
- test/functional/users_controller_test.rb
- test/matchers
- test/matchers/active_record
- test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb
- test/matchers/active_record/allow_value_matcher_test.rb
- test/matchers/active_record/association_matcher_test.rb
- test/matchers/active_record/ensure_inclusion_of_matcher_test.rb
- test/matchers/active_record/ensure_length_of_matcher_test.rb
- test/matchers/active_record/have_db_column_matcher_test.rb
- test/matchers/active_record/have_index_matcher_test.rb
- test/matchers/active_record/have_named_scope_matcher_test.rb
- test/matchers/active_record/have_readonly_attributes_matcher_test.rb
- test/matchers/active_record/validate_acceptance_of_matcher_test.rb
- test/matchers/active_record/validate_numericality_of_matcher_test.rb
- test/matchers/active_record/validate_presence_of_matcher_test.rb
- test/matchers/active_record/validate_uniqueness_of_matcher_test.rb
- test/matchers/controller
- test/matchers/controller/assign_to_matcher_test.rb
- test/matchers/controller/filter_param_matcher_test.rb
- test/matchers/controller/render_with_layout_matcher_test.rb
- test/matchers/controller/respond_with_content_type_matcher_test.rb
- test/matchers/controller/respond_with_matcher_test.rb
- test/matchers/controller/route_matcher_test.rb
- test/matchers/controller/set_session_matcher_test.rb
- test/matchers/controller/set_the_flash_matcher.rb
- test/model_builder.rb
- test/other
- test/other/autoload_macro_test.rb
- test/other/context_test.rb
- test/other/convert_to_should_syntax_test.rb
- test/other/helpers_test.rb
- test/other/private_helpers_test.rb
- test/other/should_test.rb
- test/rails_root
- test/rails_root/app
- test/rails_root/app/controllers
- test/rails_root/app/controllers/application.rb
- test/rails_root/app/controllers/posts_controller.rb
- test/rails_root/app/controllers/users_controller.rb
- test/rails_root/app/helpers
- test/rails_root/app/helpers/application_helper.rb
- test/rails_root/app/helpers/posts_helper.rb
- test/rails_root/app/helpers/users_helper.rb
- test/rails_root/app/models
- test/rails_root/app/models/address.rb
- test/rails_root/app/models/flea.rb
- test/rails_root/app/models/friendship.rb
- test/rails_root/app/models/pets
- test/rails_root/app/models/pets/dog.rb
- test/rails_root/app/models/post.rb
- test/rails_root/app/models/product.rb
- test/rails_root/app/models/tag.rb
- test/rails_root/app/models/tagging.rb
- test/rails_root/app/models/treat.rb
- test/rails_root/app/models/user.rb
- test/rails_root/app/views
- test/rails_root/app/views/layouts
- test/rails_root/app/views/layouts/posts.rhtml
- test/rails_root/app/views/layouts/users.rhtml
- test/rails_root/app/views/layouts/wide.html.erb
- test/rails_root/app/views/posts
- test/rails_root/app/views/posts/edit.rhtml
- test/rails_root/app/views/posts/index.rhtml
- test/rails_root/app/views/posts/new.rhtml
- test/rails_root/app/views/posts/show.rhtml
- test/rails_root/app/views/users
- test/rails_root/app/views/users/edit.rhtml
- test/rails_root/app/views/users/index.rhtml
- test/rails_root/app/views/users/new.rhtml
- test/rails_root/app/views/users/show.rhtml
- test/rails_root/config
- test/rails_root/config/boot.rb
- test/rails_root/config/database.yml
- test/rails_root/config/environment.rb
- test/rails_root/config/environments
- test/rails_root/config/environments/test.rb
- test/rails_root/config/initializers
- test/rails_root/config/initializers/new_rails_defaults.rb
- test/rails_root/config/initializers/shoulda.rb
- test/rails_root/config/routes.rb
- test/rails_root/db
- test/rails_root/db/migrate
- test/rails_root/db/migrate/001_create_users.rb
- test/rails_root/db/migrate/002_create_posts.rb
- test/rails_root/db/migrate/003_create_taggings.rb
- test/rails_root/db/migrate/004_create_tags.rb
- test/rails_root/db/migrate/005_create_dogs.rb
- test/rails_root/db/migrate/006_create_addresses.rb
- test/rails_root/db/migrate/007_create_fleas.rb
- test/rails_root/db/migrate/008_create_dogs_fleas.rb
- test/rails_root/db/migrate/009_create_products.rb
- test/rails_root/db/migrate/010_create_friendships.rb
- test/rails_root/db/migrate/011_create_treats.rb
- test/rails_root/db/schema.rb
- test/rails_root/log
- test/rails_root/log/sqlite3.log
- test/rails_root/log/test.log
- test/rails_root/public
- test/rails_root/public/404.html
- test/rails_root/public/422.html
- test/rails_root/public/500.html
- test/rails_root/script
- test/rails_root/script/console
- test/rails_root/script/generate
- test/rails_root/test
- test/rails_root/test/shoulda_macros
- test/rails_root/test/shoulda_macros/custom_macro.rb
- test/rails_root/vendor
- test/rails_root/vendor/gems
- test/rails_root/vendor/gems/gem_with_macro-0.0.1
- test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros
- test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb
- test/rails_root/vendor/plugins
- test/rails_root/vendor/plugins/plugin_with_macro
- test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros
- test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb
- test/README
- test/rspec_test.rb
- test/test_helper.rb
- test/unit
- test/unit/address_test.rb
- test/unit/dog_test.rb
- test/unit/flea_test.rb
- test/unit/friendship_test.rb
- test/unit/post_test.rb
- test/unit/product_test.rb
- test/unit/tag_test.rb
- test/unit/tagging_test.rb
- test/unit/user_test.rb
has_rdoc: true
homepage: http://thoughtbot.com/projects/shoulda
post_install_message:
rdoc_options:
- --line-numbers
- --main
- README.rdoc
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
rubyforge_project: shoulda
rubygems_version: 1.3.1
signing_key:
specification_version: 2
summary: Making tests easy on the fingers and eyes
test_files: []

View File

@@ -0,0 +1,12 @@
We're using GitHub[http://github.com/thoughtbot/shoulda/tree/master] and Lighthouse[http://thoughtbot.lighthouseapp.com/projects/5807], and we've been getting any combination of github pull requests, Lighthouse tickets, patches, emails, etc. We need to normalize this workflow to make sure we don't miss any fixes.
* Make sure you're accessing the source from the {official repository}[http://github.com/thoughtbot/shoulda/tree/master].
* We prefer git branches over patches, but we can take either.
* If you're using git, please make a branch for each separate contribution. We can cherry pick your commits, but pulling from a branch is easier.
* If you're submitting patches, please cut each fix or feature into a separate patch.
* There should be a Lighthouse[http://thoughtbot.lighthouseapp.com/projects/5807] ticket for any submission. If you've found a bug and want to fix it, open a new ticket at the same time.
* We've got github/lighthouse integration going, so you can update tickets when you commit. {This blog post}[http://hoth.entp.com/2008/4/11/github-and-lighthouse-sitting-in-a-tree] explains the commit options pretty well.
* Please <b>don't send pull requests</b> Just update the lighthouse ticket with the url for your fix (or attach the patch) when it's ready. The github pull requests pretty much get dropped on the floor until someone with commit rights notices them in the mailbox.
* Contributions without tests won't be accepted. The file <tt>/test/README</tt> explains the testing system pretty thoroughly.

View File

@@ -0,0 +1,22 @@
Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,169 @@
= Shoulda - Making tests easy on the fingers and eyes
Shoulda makes it easy to write elegant, understandable, and maintainable tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework. It's fully compatible with your existing tests, and requires no retooling to use.
Helpers:: #context and #should give you RSpec like test blocks.
In addition, you get nested contexts and a much more readable syntax.
Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros.
They get you started quickly, and can help you ensure that your application is conforming to best practices.
Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions.
Matchers:: Rspec-compatible matchers providing the same tests as Shoulda macros.
= Usage
=== Context Helpers (Shoulda::Context)
Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
class UserTest < Test::Unit::TestCase
context "A User instance" do
setup do
@user = User.find(:first)
end
should "return its full name" do
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?" do
assert @user.has_profile?
end
end
end
end
Produces the following test methods:
"test: A User instance should return its full name."
"test: A User instance with a profile should return true when sent #has_profile?."
So readable!
=== ActiveRecord Tests (Shoulda::ActiveRecord::Macros)
Quick macro tests for your ActiveRecord associations and validations:
class PostTest < Test::Unit::TestCase
fixtures :all
should_belong_to :user
should_have_many :tags, :through => :taggings
should_validate_uniqueness_of :title
should_validate_presence_of :body, :message => /wtf/
should_validate_presence_of :title
should_validate_numericality_of :user_id
end
class UserTest < Test::Unit::TestCase
should_have_many :posts
should_not_allow_values_for :email, "blah", "b lah"
should_allow_values_for :email, "a@b.com", "asdf@asdf.com"
should_ensure_length_in_range :email, 1..100
should_ensure_value_in_range :age, 1..100
should_not_allow_mass_assignment_of :password
end
Makes TDD so much easier.
=== Controller Tests (Shoulda::Controller::Macros)
Macros to test the most common controller patterns...
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
=== Helpful Assertions (Shoulda::Assertions)
More to come here, but have fun with what's there.
assert_same_elements([:a, :b, :c], [:c, :a, :b])
assert_contains(['a', '1'], /\d/)
assert_contains(['a', '1'], 'a')
=== 3rd Party and Application Specific Macros
Any *.rb file under RAILS_ROOT/test/shoulda_macros/ or vendor/(plugins|gems)/gem_name/shoulda_macros/ will be automatically required when you run your tests. This allows you to distribute macros with your plugins, or to organize the macros inside your application. Remember to add your macro to Test::Unit::TestCase in the macro file:
# test/shoulda_macros/security.rb
class Test::Unit::TestCase
def self.should_be_denied(opts = {})
should_set_the_flash_to(opts[:flash] || /Please log in/i)
should_redirect_to(opts[:redirect] || 'login_url')
end
end
= Rails Installation (Test::Unit)
=== As a Gem
Use this if you prefer to use versioned releases of shoulda. Specify the gem dependency in your config/environment.rb file:
Rails::Initializer.run do |config|
config.gem "thoughtbot-shoulda", :lib => "shoulda", :source => "http://gems.github.com"
end
Then:
$ rake gems:install
$ rake gems:unpack
=== As a Plugin
Use this if you prefer to use the edge version of shoulda:
$ script/plugin install git://github.com/thoughtbot/shoulda.git
=== As a Plugin (using git submodules)
Use this if you prefer the idea of being able to easily switch between using edge or a tagged version of shoulda:
$ git submodule add git://github.com/thoughtbot/shoulda.git vendor/plugins/shoulda
= Rails Installation (RSpec)
If you're using Shoulda with RSpec, we recommend that you add config.gem lines
for RSpec and Shoulda in your config/environment/test.rb file, but do not ask
Rails to load the RSpec and Shoulda libraries:
config.gem 'rspec', :lib => false
config.gem 'rspec-rails', :lib => false
config.gem 'thoughtbot-shoulda',
:lib => false,
:source => 'http://gems.github.com'
Then require shoulda from your spec/spec_helper.rb file, before Spec::Runner is
configured:
# requires for RSpec
require 'shoulda'
Spec::Runner.configure do |config|
# ...
You should not need to require anything besides the top-level shoulda library.
= Credits
Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc.
= License
Shoulda is Copyright © 2006-2008 Tammer Saleh, Thoughtbot. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.

View File

@@ -0,0 +1,72 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/gempackagetask'
$LOAD_PATH.unshift("lib")
require 'shoulda'
load 'tasks/shoulda.rake'
# Test::Unit::UI::VERBOSE
test_files_pattern = 'test/{unit,functional,other,matchers}/**/*_test.rb'
Rake::TestTask.new do |t|
t.libs << 'lib'
t.pattern = test_files_pattern
t.verbose = false
end
Rake::RDocTask.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Shoulda -- Making tests easy on the fingers and eyes"
rdoc.options << '--line-numbers'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.rdoc_files.include('README.rdoc', 'CONTRIBUTION_GUIDELINES.rdoc', 'lib/**/*.rb')
}
desc "Run code-coverage analysis using rcov"
task :coverage do
rm_rf "coverage"
files = Dir[test_files_pattern]
system "rcov --rails --sort coverage -Ilib #{files.join(' ')}"
end
desc 'Update documentation on website'
task :sync_docs => 'rdoc' do
`rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/shoulda`
end
desc 'Default: run tests.'
task :default => ['test']
spec = Gem::Specification.new do |s|
s.name = "shoulda"
s.version = Shoulda::VERSION
s.summary = "Making tests easy on the fingers and eyes"
s.homepage = "http://thoughtbot.com/projects/shoulda"
s.rubyforge_project = "shoulda"
s.files = FileList["[A-Z]*", "{bin,lib,rails,test}/**/*"]
s.executables = s.files.grep(/^bin/) { |f| File.basename(f) }
s.has_rdoc = true
s.extra_rdoc_files = ["README.rdoc", "CONTRIBUTION_GUIDELINES.rdoc"]
s.rdoc_options = ["--line-numbers", "--main", "README.rdoc"]
s.authors = ["Tammer Saleh"]
s.email = "tsaleh@thoughtbot.com"
end
Rake::GemPackageTask.new spec do |pkg|
pkg.need_tar = true
pkg.need_zip = true
end
desc "Clean files generated by rake tasks"
task :clobber => [:clobber_rdoc, :clobber_package]
desc "Generate a gemspec file for GitHub"
task :gemspec do
File.open("#{spec.name}.gemspec", 'w') do |f|
f.write spec.to_ruby
end
end

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env ruby
require 'fileutils'
require 'tmpdir'
TMP = Dir::tmpdir
def usage(msg = nil)
puts "Error: #{msg}" if msg
puts if msg
puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
puts
puts "Will convert an existing test file with names like "
puts
puts " def test_should_do_stuff"
puts " ..."
puts " end"
puts
puts "to one using the new syntax: "
puts
puts " should \"be super cool\" do"
puts " ..."
puts " end"
puts
puts "A copy of the old file will be left under #{TMP} in case\nthis script just seriously screws up"
puts
exit (msg ? 2 : 0)
end
usage("Wrong number of arguments.") unless ARGV.size == 1
usage("Temp directory '#{TMP}' is not valid. Set TMPDIR environment variable to a writeable directory.") unless File.directory?(TMP) && File.writable?(TMP)
file = ARGV.shift
tmpfile = File.join(TMP, File.basename(file))
usage("File '#{file}' doesn't exist") unless File.exists?(file)
FileUtils.cp(file, tmpfile)
contents = File.read(tmpfile)
contents.gsub!(/def test_should_(\S+)/) {|line| "should \"#{$1.tr('_', ' ')}\" do"}
contents.gsub!(/def test_(\S+)/) {|line| "should \"RENAME ME: test #{$1.tr('_', ' ')}\" do"}
File.open(file, 'w') { |f| f.write(contents) }
puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"

View File

@@ -0,0 +1,9 @@
module Shoulda
VERSION = "2.10.1"
end
if defined? Spec
require 'shoulda/rspec'
else
require 'shoulda/test_unit'
end

View File

@@ -0,0 +1,32 @@
require 'shoulda'
require 'shoulda/action_controller/helpers'
require 'shoulda/action_controller/matchers'
require 'shoulda/action_controller/macros'
require 'shoulda/action_controller/resource_options'
module Test # :nodoc: all
module Unit
class TestCase
include Shoulda::ActionController::Matchers
include Shoulda::ActionController::Helpers
extend Shoulda::ActionController::Macros
Shoulda::ActionController::VALID_FORMATS.each do |format|
include "Shoulda::ActionController::#{format.to_s.upcase}".constantize
end
end
end
end
require 'shoulda/active_record/assertions'
require 'shoulda/action_mailer/assertions'
module ActionController #:nodoc: all
module Integration
class Session
include Shoulda::Assertions
include Shoulda::Helpers
include Shoulda::ActiveRecord::Assertions
include Shoulda::ActionMailer::Assertions
end
end
end

View File

@@ -0,0 +1,199 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module HTML # :nodoc: all
def self.included(other)
other.class_eval do
extend Shoulda::ActionController::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
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_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
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_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_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
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
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
end
end
end
end
end
end
end

View File

@@ -0,0 +1,168 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module XML
def self.included(other) #:nodoc:
other.class_eval do
extend Shoulda::ActionController::XML::ClassMethods
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

View File

@@ -0,0 +1,64 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Helpers # :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

View File

@@ -0,0 +1,296 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
# = 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
module Macros
include Matchers
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
# 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)
matcher = set_the_flash.to(val)
if val
should matcher.description do
assert_accepts matcher, @controller
end
else
should "not #{matcher.description}" do
assert_rejects matcher, @controller
end
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 filter_parameter_logging
#
# is set for the specified keys
#
# Example:
#
# should_filter_params :password, :ssn
def should_filter_params(*keys)
keys.each do |key|
matcher = filter_param(key)
should matcher.description do
assert_accepts matcher, @controller
end
end
end
# Macro that creates a test asserting that the controller assigned to
# each of the named instance variable(s).
#
# Options:
# * <tt>:class</tt> - The expected class of the instance variable being checked.
# * <tt>:equals</tt> - A string which is evaluated and compared for equality with
# the instance variable being checked.
#
# Example:
#
# should_assign_to :user, :posts
# should_assign_to :user, :class => User
# should_assign_to(:user) { @user }
def should_assign_to(*names, &block)
opts = names.extract_options!
if opts[:equals]
warn "[DEPRECATION] should_assign_to :var, :equals => 'val' " <<
"is deprecated. Use should_assign_to(:var) { 'val' } instead."
end
names.each do |name|
matcher = assign_to(name).with_kind_of(opts[:class])
test_name = matcher.description
test_name << " which is equal to #{opts[:equals]}" if opts[:equals]
should test_name do
if opts[:equals]
instantiate_variables_from_assigns do
expected_value = eval(opts[:equals],
self.send(:binding),
__FILE__,
__LINE__)
matcher = matcher.with(expected_value)
end
elsif block
expected_value = instance_eval(&block)
matcher = matcher.with(expected_value)
end
assert_accepts matcher, @controller
end
end
end
# Macro that creates a test asserting that the controller did not assign to
# any of the named instance variable(s).
#
# Example:
#
# should_not_assign_to :user, :posts
def should_not_assign_to(*names)
names.each do |name|
matcher = assign_to(name)
should "not #{matcher.description}" do
assert_rejects matcher, @controller
end
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
matcher = respond_with(response)
assert_accepts matcher, @controller
end
end
# Macro that creates a test asserting that the response content type was 'content_type'.
# Example:
#
# should_respond_with_content_type 'application/rss+xml'
# should_respond_with_content_type :rss
# should_respond_with_content_type /rss/
def should_respond_with_content_type(content_type)
should "respond with content type of #{content_type}" do
matcher = respond_with_content_type(content_type)
assert_accepts matcher, @controller
end
end
# Macro that creates a test asserting that a value returned from the session is correct.
# 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_set_session(:user_id) { '@user.id' }
# should_set_session(:message) { "Free stuff" }
def should_set_session(key, expected = nil, &block)
matcher = set_session(key)
if expected
warn "[DEPRECATION] should_set_session :key, 'val' is deprecated. " <<
"Use should_set_session(:key) { 'val' } instead."
end
should matcher.description do
if expected
instantiate_variables_from_assigns do
expected_value = eval(expected,
self.send(:binding),
__FILE__,
__LINE__)
matcher = matcher.to(expected_value)
end
else
expected_value = instance_eval(&block)
matcher = matcher.to(expected_value)
end
assert_accepts matcher, @controller
end
end
# Deprecated. See should_set_session
def should_return_from_session(key, expected)
warn "[DEPRECATION] should_require_attributes is deprecated. " <<
"Use should_set_session instead."
should_set_session(key, expected)
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 rendered with the given layout.
# Example:
#
# should_render_with_layout 'special'
def should_render_with_layout(expected_layout = 'application')
matcher = render_with_layout(expected_layout)
if expected_layout
should matcher.description do
assert_accepts matcher, @controller
end
else
should "render without layout" do
assert_rejects matcher, @controller
end
end
end
# Macro that creates a test asserting that the controller rendered without a layout.
# Same as @should_render_with_layout false@
def should_render_without_layout
should_render_with_layout nil
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("the user's profile") { user_url(@user) }
def should_redirect_to(description, &block)
unless block
warn "[DEPRECATION] should_redirect_to without a block is " <<
"deprecated. Use should_redirect_to('somewhere') { } instead."
end
should "redirect to #{description}" do
if block
url = instance_eval(&block)
else
instantiate_variables_from_assigns do
url = eval(description, self.send(:binding), __FILE__, __LINE__)
end
end
assert_redirected_to url
end
end
# Macro that creates a routing test. It tries to use the given HTTP
# +method+ on the given +path+, and asserts that it routes to the
# given +options+.
#
# If you don't specify a :controller, it will try to guess the controller
# based on the current test.
#
# +to_param+ is called on the +options+ given.
#
# Examples:
#
# should_route :get, "/posts", :controller => :posts, :action => :index
# should_route :get, "/posts/new", :action => :new
# should_route :post, "/posts", :action => :create
# should_route :get, "/posts/1", :action => :show, :id => 1
# should_route :edit, "/posts/1", :action => :show, :id => 1
# should_route :put, "/posts/1", :action => :update, :id => 1
# should_route :delete, "/posts/1", :action => :destroy, :id => 1
# should_route :get, "/users/1/posts/1",
# :action => :show, :id => 1, :user_id => 1
#
def should_route(method, path, options)
unless options[:controller]
options[:controller] = self.name.gsub(/ControllerTest$/, '').tableize
end
matcher = route(method, path).to(options)
should matcher.description do
assert_accepts matcher.in_context(self), self
end
end
end
end
end

View File

@@ -0,0 +1,37 @@
require 'shoulda/action_controller/matchers/assign_to_matcher'
require 'shoulda/action_controller/matchers/filter_param_matcher'
require 'shoulda/action_controller/matchers/set_the_flash_matcher'
require 'shoulda/action_controller/matchers/render_with_layout_matcher'
require 'shoulda/action_controller/matchers/respond_with_matcher'
require 'shoulda/action_controller/matchers/respond_with_content_type_matcher'
require 'shoulda/action_controller/matchers/set_session_matcher'
require 'shoulda/action_controller/matchers/route_matcher'
module Shoulda # :nodoc:
module ActionController # :nodoc:
# By using the macro helpers 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 Matchers
end
end
end

View File

@@ -0,0 +1,109 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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
end
def with_kind_of(expected_class)
@expected_class = expected_class
self
end
def with(expected_value)
@expected_value = expected_value
self
end
def matches?(controller)
@controller = controller
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
private
def assigned_value?
if assigned_value.nil?
@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 @expected_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
assigns[@variable]
end
def assigns
@controller.response.template.assigns
end
end
end
end
end

View File

@@ -0,0 +1,57 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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_params? && filters_key?
end
def failure_message
"Expected #{@key} to be filtered"
end
def negative_failure_message
"Did not expect #{@key} to be filtered"
end
def description
"filter #{@key}"
end
private
def filters_params?
@controller.respond_to?(:filter_parameters)
end
def filters_key?
filtered_value == '[FILTERED]'
end
def filtered_value
filtered = @controller.send(:filter_parameters,
@key.to_s => @key.to_s)
filtered[@key.to_s]
end
end
end
end
end

View File

@@ -0,0 +1,81 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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(layout = nil)
RenderWithLayout.new(layout)
end
class RenderWithLayout # :nodoc:
def initialize(layout)
@layout = layout.to_s unless layout.nil?
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 @layout.nil?
description << "a layout"
else
description << "the #{@layout.inspect} layout"
end
description
end
private
def rendered_with_layout?
!layout.blank?
end
def rendered_with_expected_layout?
return true if @layout.nil?
layout == @layout
end
def layout
layout = @controller.response.layout
if layout.nil?
nil
else
layout.split('/').last
end
end
def expectation
"to #{description}"
end
def result
if rendered_with_layout?
"rendered with the #{layout.inspect} layout"
else
"rendered without a layout"
end
end
end
end
end
end

View File

@@ -0,0 +1,70 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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 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
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

View File

@@ -0,0 +1,81 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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
::ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[potential_symbol]
else
potential_symbol
end
end
def expectation
"response to be a #{@status}, but was #{response_code}"
end
end
end
end
end

View File

@@ -0,0 +1,93 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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
self
end
def in_context(context)
@context = context
self
end
def matches?(controller)
@controller = controller
guess_controller!
stringify_params!
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.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

View File

@@ -0,0 +1,87 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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)
@value = value
self
end
def matches?(controller)
@controller = controller
(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
private
def assigned_value?
!assigned_value.blank?
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
@controller.response.session.data
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

View File

@@ -0,0 +1,85 @@
module Shoulda # :nodoc:
module ActionController # :nodoc:
module Matchers
# 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_not set_the_flash }
def set_the_flash
SetTheFlashMatcher.new
end
class SetTheFlashMatcher # :nodoc:
def to(value)
@value = value
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
@controller.response.session['flash']
end
def expectation
expectation = "the flash 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

View File

@@ -0,0 +1,233 @@
module Shoulda # :nodoc:
module ActionController
# 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/action_controller/formats/#{f}"}
# 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.
#
# Example:
# class UsersControllerTest < Test::Unit::TestCase
# fixtures :all
#
# def setup
# ...normal setup code...
# @user = User.find(:first)
# 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.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
#
# 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
end
end

View File

@@ -0,0 +1,10 @@
require 'shoulda'
require 'shoulda/action_mailer/assertions'
module Test # :nodoc: all
module Unit
class TestCase
include Shoulda::ActionMailer::Assertions
end
end
end

View File

@@ -0,0 +1,38 @@
module Shoulda # :nodoc:
module ActionMailer # :nodoc:
module Assertions
# 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
end
end
end

View File

@@ -0,0 +1,10 @@
require 'shoulda'
require 'shoulda/action_view/macros'
module Test # :nodoc: all
module Unit
class TestCase
extend Shoulda::ActionView::Macros
end
end
end

View File

@@ -0,0 +1,56 @@
module Shoulda # :nodoc:
module ActionView # :nodoc:
# = Macro test helpers for your view
#
# 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 :new" do
# setup do
# get :new
# end
#
# should_render_a_form
# should_render_page_with_metadata :title => /index/
#
# should "do something else really cool" do
# assert_select '#really_cool'
# end
# end
#
# Would produce 3 tests for the +show+ action
module Macros
# 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
# Macro that creates a test asserting that the rendered view contains the selected metatags.
# Values can be string or Regexps.
# Example:
#
# should_render_page_with_metadata :description => "Description of this page", :keywords => /post/
#
# You can also use this method to test the rendered views title.
#
# Example:
# should_render_page_with_metadata :title => /index/
def should_render_page_with_metadata(options)
options.each do |key, value|
should "have metatag #{key}" do
if key.to_sym == :title
assert_select "title", value
else
assert_select "meta[name=?][content#{"*" if value.is_a?(Regexp)}=?]", key, value
end
end
end
end
end
end
end

View File

@@ -0,0 +1,16 @@
require 'shoulda'
require 'shoulda/active_record/helpers'
require 'shoulda/active_record/matchers'
require 'shoulda/active_record/assertions'
require 'shoulda/active_record/macros'
module Test # :nodoc: all
module Unit
class TestCase
include Shoulda::ActiveRecord::Helpers
include Shoulda::ActiveRecord::Matchers
include Shoulda::ActiveRecord::Assertions
extend Shoulda::ActiveRecord::Macros
end
end
end

View File

@@ -0,0 +1,69 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Assertions
# 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 Active Record model validates with the passed
# <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
# contained within the list of errors for that attribute.
#
# assert_good_value(User.new, :email, "user@example.com")
# assert_good_value(User.new, :ssn, "123456789", /length/)
#
# If a class is passed as the first argument, a new object will be
# instantiated before the assertion. If an instance variable exists with
# the same name as the class (underscored), that object will be used
# instead.
#
# assert_good_value(User, :email, "user@example.com")
#
# @product = Product.new(:tangible => false)
# assert_good_value(Product, :price, "0")
def assert_good_value(object_or_klass, attribute, value, error_message_to_avoid = nil)
object = get_instance_of(object_or_klass)
matcher = allow_value(value).
for(attribute).
with_message(error_message_to_avoid)
assert_accepts(matcher, object)
end
# Asserts that an Active Record model invalidates the passed
# <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
# contained within the list of errors for that attribute.
#
# assert_bad_value(User.new, :email, "invalid")
# assert_bad_value(User.new, :ssn, "123", /length/)
#
# If a class is passed as the first argument, a new object will be
# instantiated before the assertion. If an instance variable exists with
# the same name as the class (underscored), that object will be used
# instead.
#
# assert_bad_value(User, :email, "invalid")
#
# @product = Product.new(:tangible => true)
# assert_bad_value(Product, :price, "0")
def assert_bad_value(object_or_klass, attribute, value,
error_message_to_expect = nil)
object = get_instance_of(object_or_klass)
matcher = allow_value(value).
for(attribute).
with_message(error_message_to_expect)
assert_rejects(matcher, object)
end
end
end
end

View File

@@ -0,0 +1,40 @@
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
def get_instance_of(object_or_klass)
if object_or_klass.is_a?(Class)
klass = object_or_klass
instance_variable_get("@#{instance_variable_name_for(klass)}") || klass.new
else
object_or_klass
end
end
def instance_variable_name_for(klass)
klass.to_s.split('::').last.underscore
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
I18n.translate("activerecord.errors.messages.#{key}", values)
else # Rails <= 2.1.x
::ActiveRecord::Errors.default_error_messages[key] % values[:count]
end
end
end
end
end

View File

@@ -0,0 +1,589 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
# = Macro test helpers for your active record models
#
# These helpers will test most of the validations and associations for your ActiveRecord models.
#
# class UserTest < Test::Unit::TestCase
# should_validate_presence_of :name, :phone_number
# should_not_allow_values_for :phone_number, "abcd", "1234"
# should_allow_values_for :phone_number, "(123) 456-7890"
#
# should_not_allow_mass_assignment_of :password
#
# should_have_one :profile
# should_have_many :dogs
# should_have_many :messes, :through => :dogs
# should_belong_to :lover
# end
#
# For all of these helpers, the last parameter may be a hash of options.
#
module Macros
include Helpers
include Matchers
# Ensures that the model cannot be saved if one of the attributes listed is not present.
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
#
# Example:
# should_validate_presence_of :name, :phone_number
#
def should_validate_presence_of(*attributes)
message = get_options!(attributes, :message)
klass = model_class
attributes.each do |attribute|
matcher = validate_presence_of(attribute).with_message(message)
should matcher.description do
assert_accepts(matcher, get_instance_of(klass))
end
end
end
# Deprecated. See should_validate_presence_of
def should_require_attributes(*attributes)
warn "[DEPRECATION] should_require_attributes is deprecated. " <<
"Use should_validate_presence_of instead."
should_validate_presence_of(*attributes)
end
# Ensures that the model cannot be saved if one of the attributes listed is not unique.
# Requires an existing record
#
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
# * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
# * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
# exact match. Ignored by non-text attributes. Default = <tt>true</tt>
#
# Examples:
# should_validate_uniqueness_of :keyword, :username
# should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
# should_validate_uniqueness_of :email, :scoped_to => :name
# should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
# should_validate_uniqueness_of :email, :case_sensitive => false
#
def should_validate_uniqueness_of(*attributes)
message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
scope = [*scope].compact
case_sensitive = true if case_sensitive.nil?
klass = model_class
attributes.each do |attribute|
matcher = validate_uniqueness_of(attribute).
with_message(message).scoped_to(scope)
matcher = matcher.case_insensitive unless case_sensitive
should matcher.description do
assert_accepts(matcher, get_instance_of(klass))
end
end
end
# Deprecated. See should_validate_uniqueness_of
def should_require_unique_attributes(*attributes)
warn "[DEPRECATION] should_require_unique_attributes is deprecated. " <<
"Use should_validate_uniqueness_of instead."
should_validate_uniqueness_of(*attributes)
end
# Ensures that the attribute can be set on mass update.
#
# should_allow_mass_assignment_of :first_name, :last_name
#
def should_allow_mass_assignment_of(*attributes)
get_options!(attributes)
klass = model_class
attributes.each do |attribute|
matcher = allow_mass_assignment_of(attribute)
should matcher.description do
assert_accepts matcher, klass.new
end
end
end
# Ensures that the attribute cannot be set on mass update.
#
# should_not_allow_mass_assignment_of :password, :admin_flag
#
def should_not_allow_mass_assignment_of(*attributes)
get_options!(attributes)
klass = model_class
attributes.each do |attribute|
matcher = allow_mass_assignment_of(attribute)
should "not #{matcher.description}" do
assert_rejects matcher, klass.new
end
end
end
# Deprecated. See should_not_allow_mass_assignment_of
def should_protect_attributes(*attributes)
warn "[DEPRECATION] should_protect_attributes is deprecated. " <<
"Use should_not_allow_mass_assignment_of instead."
should_not_allow_mass_assignment_of(*attributes)
end
# Ensures that the attribute cannot be changed once the record has been created.
#
# should_have_readonly_attributes :password, :admin_flag
#
def should_have_readonly_attributes(*attributes)
get_options!(attributes)
klass = model_class
attributes.each do |attribute|
matcher = have_readonly_attribute(attribute)
should matcher.description do
assert_accepts matcher, klass.new
end
end
end
# Ensures that the attribute cannot be set to the given values
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
#
# Example:
# should_not_allow_values_for :isbn, "bad 1", "bad 2"
#
def should_not_allow_values_for(attribute, *bad_values)
message = get_options!(bad_values, :message)
klass = model_class
bad_values.each do |value|
matcher = allow_value(value).for(attribute).with_message(message)
should "not #{matcher.description}" do
assert_rejects matcher, get_instance_of(klass)
end
end
end
# Ensures that the attribute can be set to the given values.
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Example:
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
#
def should_allow_values_for(attribute, *good_values)
get_options!(good_values)
klass = model_class
klass = model_class
good_values.each do |value|
matcher = allow_value(value).for(attribute)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
end
# Ensures that the length of the attribute is in the given range
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
# * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
#
# Example:
# should_ensure_length_in_range :password, (6..20)
#
def should_ensure_length_in_range(attribute, range, opts = {})
short_message, long_message = get_options!([opts],
:short_message,
:long_message)
klass = model_class
matcher = ensure_length_of(attribute).
is_at_least(range.first).
with_short_message(short_message).
is_at_most(range.last).
with_long_message(long_message)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
# Ensures that the length of the attribute is at least a certain length
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
#
# Example:
# should_ensure_length_at_least :name, 3
#
def should_ensure_length_at_least(attribute, min_length, opts = {})
short_message = get_options!([opts], :short_message)
klass = model_class
matcher = ensure_length_of(attribute).
is_at_least(min_length).
with_short_message(short_message)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
# Ensures that the length of the attribute is exactly a certain length
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
#
# Example:
# should_ensure_length_is :ssn, 9
#
def should_ensure_length_is(attribute, length, opts = {})
message = get_options!([opts], :message)
klass = model_class
matcher = ensure_length_of(attribute).
is_equal_to(length).
with_message(message)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
# Ensure that the attribute is in the range specified
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
# * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
#
# Example:
# should_ensure_value_in_range :age, (0..100)
#
def should_ensure_value_in_range(attribute, range, opts = {})
message, low_message, high_message = get_options!([opts],
:message,
:low_message,
:high_message)
klass = model_class
matcher = ensure_inclusion_of(attribute).
in_range(range).
with_message(message).
with_low_message(low_message).
with_high_message(high_message)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
# Ensure that the attribute is numeric
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
#
# Example:
# should_validate_numericality_of :age
#
def should_validate_numericality_of(*attributes)
message = get_options!(attributes, :message)
klass = model_class
attributes.each do |attribute|
matcher = validate_numericality_of(attribute).
with_message(message)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
end
# Deprecated. See should_validate_numericality_of
def should_only_allow_numeric_values_for(*attributes)
warn "[DEPRECATION] should_only_allow_numeric_values_for is " <<
"deprecated. Use should_validate_numericality_of instead."
should_validate_numericality_of(*attributes)
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:
# should_have_many :friends
# should_have_many :enemies, :through => :friends
# should_have_many :enemies, :dependent => :destroy
#
def should_have_many(*associations)
through, dependent = get_options!(associations, :through, :dependent)
klass = model_class
associations.each do |association|
matcher = have_many(association).through(through).dependent(dependent)
should matcher.description do
assert_accepts(matcher, klass.new)
end
end
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:
# should_have_one :god # unless hindu
#
def should_have_one(*associations)
dependent = get_options!(associations, :dependent)
klass = model_class
associations.each do |association|
matcher = have_one(association).dependent(dependent)
should matcher.description do
assert_accepts(matcher, klass.new)
end
end
end
# Ensures that the has_and_belongs_to_many relationship exists, and that the join
# table is in place.
#
# should_have_and_belong_to_many :posts, :cars
#
def should_have_and_belong_to_many(*associations)
get_options!(associations)
klass = model_class
associations.each do |association|
matcher = have_and_belong_to_many(association)
should matcher.description do
assert_accepts(matcher, klass.new)
end
end
end
# Ensure that the belongs_to relationship exists.
#
# should_belong_to :parent
#
def should_belong_to(*associations)
dependent = get_options!(associations, :dependent)
klass = model_class
associations.each do |association|
matcher = belong_to(association).dependent(dependent)
should matcher.description do
assert_accepts(matcher, klass.new)
end
end
end
# Ensure that the given class methods are defined on the model.
#
# should_have_class_methods :find, :destroy
#
def should_have_class_methods(*methods)
get_options!(methods)
klass = model_class
methods.each do |method|
should "respond to class method ##{method}" do
assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
end
end
end
# Ensure that the given instance methods are defined on the model.
#
# should_have_instance_methods :email, :name, :name=
#
def should_have_instance_methods(*methods)
get_options!(methods)
klass = model_class
methods.each do |method|
should "respond to instance method ##{method}" do
assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
end
end
end
# Ensure that the given columns are defined on the models backing SQL table.
# Also aliased to should_have_index for readability.
# Takes the same options available in migrations:
# :type, :precision, :limit, :default, :null, and :scale
#
# Examples:
#
# should_have_db_columns :id, :email, :name, :created_at
#
# should_have_db_column :email, :type => "string", :limit => 255
# should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
# should_have_db_column :admin, :default => false, :null => false
#
def should_have_db_columns(*columns)
column_type, precision, limit, default, null, scale, sql_type =
get_options!(columns, :type, :precision, :limit,
:default, :null, :scale, :sql_type)
klass = model_class
columns.each do |name|
matcher = have_db_column(name).
of_type(column_type).
with_options(:precision => precision, :limit => limit,
:default => default, :null => null,
:scale => scale, :sql_type => sql_type)
should matcher.description do
assert_accepts(matcher, klass.new)
end
end
end
alias_method :should_have_db_column, :should_have_db_columns
# Ensures that there are DB indices on the given columns or tuples of columns.
# Also aliased to should_have_index for readability
#
# 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:
#
# should_have_indices :email, :name, [:commentable_type, :commentable_id]
# should_have_index :age
# should_have_index :ssn, :unique => true
#
def should_have_indices(*columns)
unique = get_options!(columns, :unique)
klass = model_class
columns.each do |column|
matcher = have_index(column).unique(unique)
should matcher.description do
assert_accepts(matcher, klass.new)
end
end
end
alias_method :should_have_index, :should_have_indices
# Ensures that the model cannot be saved if one of the attributes listed is not accepted.
#
# If an instance variable has been created in the setup named after the
# model being tested, then this method will use that. Otherwise, it will
# create a new instance to test against.
#
# Options:
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
#
# Example:
# should_validate_acceptance_of :eula
#
def should_validate_acceptance_of(*attributes)
message = get_options!(attributes, :message)
klass = model_class
attributes.each do |attribute|
matcher = validate_acceptance_of(attribute).with_message(message)
should matcher.description do
assert_accepts matcher, get_instance_of(klass)
end
end
end
# Deprecated. See should_validate_uniqueness_of
def should_require_acceptance_of(*attributes)
warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
"Use should_validate_acceptance_of instead."
should_validate_acceptance_of(*attributes)
end
# Ensures that the model has a method named scope_name that returns a NamedScope object with the
# proxy options set to the options you supply. scope_name can be either a symbol, or a method
# call which will be evaled against the model. The eval'd method call has access to all the same
# instance variables that a should statement would.
#
# Options: Any of the options that the named scope would pass on to find.
#
# Example:
#
# should_have_named_scope :visible, :conditions => {:visible => true}
#
# Passes for
#
# named_scope :visible, :conditions => {:visible => true}
#
# Or for
#
# def self.visible
# scoped(:conditions => {:visible => true})
# end
#
# You can test lambdas or methods that return ActiveRecord#scoped calls:
#
# should_have_named_scope 'recent(5)', :limit => 5
# should_have_named_scope 'recent(1)', :limit => 1
#
# Passes for
# named_scope :recent, lambda {|c| {:limit => c}}
#
# Or for
#
# def self.recent(c)
# scoped(:limit => c)
# end
#
def should_have_named_scope(scope_call, find_options = nil)
klass = model_class
matcher = have_named_scope(scope_call).finding(find_options)
should matcher.description do
assert_accepts matcher.in_context(self), klass.new
end
end
end
end
end

View File

@@ -0,0 +1,42 @@
require 'shoulda/active_record/helpers'
require 'shoulda/active_record/matchers/validation_matcher'
require 'shoulda/active_record/matchers/allow_value_matcher'
require 'shoulda/active_record/matchers/ensure_length_of_matcher'
require 'shoulda/active_record/matchers/ensure_inclusion_of_matcher'
require 'shoulda/active_record/matchers/validate_presence_of_matcher'
require 'shoulda/active_record/matchers/validate_uniqueness_of_matcher'
require 'shoulda/active_record/matchers/validate_acceptance_of_matcher'
require 'shoulda/active_record/matchers/validate_numericality_of_matcher'
require 'shoulda/active_record/matchers/association_matcher'
require 'shoulda/active_record/matchers/have_db_column_matcher'
require 'shoulda/active_record/matchers/have_index_matcher'
require 'shoulda/active_record/matchers/have_readonly_attribute_matcher'
require 'shoulda/active_record/matchers/allow_mass_assignment_of_matcher'
require 'shoulda/active_record/matchers/have_named_scope_matcher'
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
# = 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 Matchers
end
end
end

View File

@@ -0,0 +1,83 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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?
@failure_message = "#{@attribute} was made accessible"
else
if protected_attributes.empty?
@failure_message = "no attributes were protected"
else
@failure_message = "#{class_name} is protecting " <<
"#{protected_attributes.to_a.to_sentence}, " <<
"but not #{@attribute}."
end
end
true
else
if whitelisting?
@negative_failure_message =
"Expected #{@attribute} to be accessible"
else
@negative_failure_message =
"Did not expect #{@attribute} to be protected"
end
false
end
end
attr_reader :failure_message, :negative_failure_message
def description
"protect #{@attribute} from mass updates"
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

View File

@@ -0,0 +1,102 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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. Defaults to the
# translation for :invalid.
#
# 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
@expected_message ||= :invalid
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 = @instance.errors.on(@attribute)
@errors = [@errors] unless @errors.is_a?(Array)
errors_match_regexp? || errors_match_string?
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 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

View File

@@ -0,0 +1,226 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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?
"#{model_class.name} does not have any relationship to #{@through}"
false
else
true
end
end
def through_association_correct?
if @through == reflection.options[:through]
"Expected #{model_class.name} to have #{@name} through #{@through}, " <<
" but got it through #{reflection.options[:through]}"
true
else
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

View File

@@ -0,0 +1,87 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,141 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,169 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,105 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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_index(:age) }
# it { should have_index([:commentable_type, :commentable_id]) }
# it { should have_index(:ssn).unique(true) }
#
def have_index(columns)
HaveIndexMatcher.new(:have_index, columns)
end
class HaveIndexMatcher # :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}"
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
@unique ? "unique" : "non-unique"
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

View File

@@ -0,0 +1,125 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# Ensures that the model has a method named scope_call that returns a
# NamedScope object with the proxy options set to the options you supply.
# scope_call can be either a symbol, or a Ruby expression in a String
# which will be evaled. The eval'd method call has access to all the same
# instance variables that an example would.
#
# Options:
#
# * <tt>in_context</tt> - Any of the options that the named scope would
# pass on to find.
#
# Example:
#
# it { should have_named_scope(:visible).
# finding(:conditions => {:visible => true}) }
#
# Passes for
#
# named_scope :visible, :conditions => {:visible => true}
#
# Or for
#
# def self.visible
# scoped(:conditions => {:visible => true})
# end
#
# You can test lambdas or methods that return ActiveRecord#scoped calls:
#
# it { should have_named_scope('recent(5)').finding(:limit => 5) }
# it { should have_named_scope('recent(1)').finding(:limit => 1) }
#
# Passes for
# named_scope :recent, lambda {|c| {:limit => c}}
#
# Or for
#
# def self.recent(c)
# scoped(:limit => c)
# end
#
def have_named_scope(scope_call)
HaveNamedScopeMatcher.new(scope_call).in_context(self)
end
class HaveNamedScopeMatcher # :nodoc:
def initialize(scope_call)
@scope_call = scope_call.to_s
end
def finding(finding)
@finding = finding
self
end
def in_context(context)
@context = context
self
end
def matches?(subject)
@subject = subject
call_succeeds? && returns_scope? && finds_correct_scope?
end
def failure_message
"Expected #{@missing_expectation}"
end
def negative_failure_message
"Didn't expect a named scope for #{@scope_call}"
end
def description
result = "have a named scope for #{@scope_call}"
result << " finding #{@finding.inspect}" unless @finding.nil?
result
end
private
def call_succeeds?
scope
true
rescue Exception => exception
@missing_expectation = "#{@subject.class.name} " <<
"to respond to #{@scope_call} " <<
"but raised error: #{exception.inspect}"
false
end
def scope
@scope ||= @context.instance_eval("#{@subject.class.name}.#{@scope_call}")
end
def returns_scope?
if ::ActiveRecord::NamedScope::Scope === scope
true
else
@missing_expectation = "#{@scope_call} to return a scope"
false
end
end
def finds_correct_scope?
return true if @finding.nil?
if @finding == scope.proxy_options
true
else
@missing_expectation = "#{@scope_call} to return results scoped to "
@missing_expectation << "#{@finding.inspect} but was scoped to "
@missing_expectation << scope.proxy_options.inspect
false
end
end
end
end
end
end

View File

@@ -0,0 +1,59 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,41 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,39 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,60 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,148 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
# 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

View File

@@ -0,0 +1,56 @@
module Shoulda # :nodoc:
module ActiveRecord # :nodoc:
module Matchers
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

View File

@@ -0,0 +1,59 @@
module Shoulda # :nodoc:
module Assertions
# 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 matcher returns true when +target+ is passed to #matches?
def assert_accepts(matcher, target)
success = matcher.matches?(target)
assert_block(matcher.failure_message) { success }
end
# Asserts that the given matcher returns false when +target+ is passed to #matches?
def assert_rejects(matcher, target)
success = !matcher.matches?(target)
assert_block(matcher.negative_failure_message) { success }
end
end
end

View File

@@ -0,0 +1,46 @@
module Shoulda # :nodoc:
# Call autoload_macros when you want to load test macros automatically in a non-Rails
# project (it's done automatically for Rails projects).
# You don't need to specify ROOT/test/shoulda_macros explicitly. Your custom macros
# are loaded automatically when you call autoload_macros.
#
# The first argument is the path to you application's root directory.
# All following arguments are directories relative to your root, which contain
# shoulda_macros subdirectories. These directories support the same kinds of globs as the
# Dir class.
#
# Basic usage (from a test_helper):
# Shoulda.autoload_macros(File.dirname(__FILE__) + '/..')
# will load everything in
# - your_app/test/shoulda_macros
#
# To load vendored macros as well:
# Shoulda.autoload_macros(APP_ROOT, 'vendor/*')
# will load everything in
# - APP_ROOT/vendor/*/shoulda_macros
# - APP_ROOT/test/shoulda_macros
#
# To load macros in an app with a vendor directory laid out like Rails':
# Shoulda.autoload_macros(APP_ROOT, 'vendor/{plugins,gems}/*')
# or
# Shoulda.autoload_macros(APP_ROOT, 'vendor/plugins/*', 'vendor/gems/*')
# will load everything in
# - APP_ROOT/vendor/plugins/*/shoulda_macros
# - APP_ROOT/vendor/gems/*/shoulda_macros
# - APP_ROOT/test/shoulda_macros
#
# If you prefer to stick testing dependencies away from your production dependencies:
# Shoulda.autoload_macros(APP_ROOT, 'vendor/*', 'test/vendor/*')
# will load everything in
# - APP_ROOT/vendor/*/shoulda_macros
# - APP_ROOT/test/vendor/*/shoulda_macros
# - APP_ROOT/test/shoulda_macros
def self.autoload_macros(root, *dirs)
dirs << File.join('test')
complete_dirs = dirs.map{|d| File.join(root, d, 'shoulda_macros')}
all_files = complete_dirs.inject([]){ |files, dir| files + Dir[File.join(dir, '*.rb')] }
all_files.each do |file|
require file
end
end
end

View File

@@ -0,0 +1,304 @@
module Shoulda
class << self
attr_accessor :contexts
def contexts # :nodoc:
@contexts ||= []
end
def current_context # :nodoc:
self.contexts.last
end
def add_context(context) # :nodoc:
self.contexts.push(context)
end
def remove_context # :nodoc:
self.contexts.pop
end
end
module ClassMethods
# == 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 can also take a Proc as a <tt>:before </tt>option. This proc runs after any
# parent context's setups but before the current context's setup.
#
# === Example:
#
# context "Some context" do
# setup { puts("I run after the :before proc") }
#
# should "run a :before proc", :before => lambda { puts("I run before the setup") } do
# assert true
# end
# end
def should(name, options = {}, &blk)
if Shoulda.current_context
block_given? ? Shoulda.current_context.should(name, options, &blk) : Should.current_context.should_eventually(name)
else
context_name = self.name.gsub(/Test/, "")
context = Shoulda::Context.new(context_name, self) do
block_given? ? should(name, options, &blk) : should_eventually(name)
end
context.build
end
end
# == Before statements
#
# Before statements are should statements that run before the current
# context's setup. These are especially useful when setting expectations.
#
# === Example:
#
# class UserControllerTest < Test::Unit::TestCase
# context "the index action" do
# setup do
# @users = [Factory(:user)]
# User.stubs(:find).returns(@users)
# end
#
# context "on GET" do
# setup { get :index }
#
# should_respond_with :success
#
# # runs before "get :index"
# before_should "find all users" do
# User.expects(:find).with(:all).returns(@users)
# end
# end
# end
# end
def before_should(name, &blk)
should(name, :before => blk) { assert true }
end
# Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
def should_eventually(name, options = {}, &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
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_blocks # blocks given via setup methods
attr_accessor :teardown_blocks # blocks given via teardown methods
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.add_context(self)
self.name = name
self.parent = parent
self.setup_blocks = []
self.teardown_blocks = []
self.shoulds = []
self.should_eventuallys = []
self.subcontexts = []
merge_block(&blk)
Shoulda.remove_context
end
def merge_block(&blk)
blk.bind(self).call
end
def context(name, &blk)
self.subcontexts << Context.new(name, self, &blk)
end
def setup(&blk)
self.setup_blocks << blk
end
def teardown(&blk)
self.teardown_blocks << blk
end
def should(name, options = {}, &blk)
if block_given?
self.shoulds << { :name => name, :before => options[:before], :block => blk }
else
self.should_eventuallys << { :name => name }
end
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
begin
context.run_parent_setup_blocks(self)
should[:before].bind(self).call if should[:before]
context.run_current_setup_blocks(self)
should[:block].bind(self).call
ensure
context.run_all_teardown_blocks(self)
end
end
end
def run_all_setup_blocks(binding)
run_parent_setup_blocks(binding)
run_current_setup_blocks(binding)
end
def run_parent_setup_blocks(binding)
self.parent.run_all_setup_blocks(binding) if am_subcontext?
end
def run_current_setup_blocks(binding)
setup_blocks.each do |setup_block|
setup_block.bind(binding).call
end
end
def run_all_teardown_blocks(binding)
teardown_blocks.reverse.each do |teardown_block|
teardown_block.bind(binding).call
end
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
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

View File

@@ -0,0 +1,8 @@
module Shoulda # :nodoc:
module Helpers
# Prints a message to stdout, tagged with the name of the calling method.
def report!(msg = "")
puts("#{caller.first}: #{msg}")
end
end
end

View File

@@ -0,0 +1,73 @@
require 'shoulda/private_helpers'
module Shoulda # :nodoc:
module Macros
# Macro that creates a test asserting a change between the return value
# of an expression that is run before and after the current setup block
# is run. This is similar to Active Support's <tt>assert_difference</tt>
# assertion, but supports more than just numeric values. See also
# should_not_change.
#
# Example:
#
# context "Creating a post" do
# setup { Post.create }
# should_change "Post.count", :by => 1
# end
#
# As shown in this example, the <tt>:by</tt> option expects a numeric
# difference between the before and after values of the expression. You
# may also specify <tt>:from</tt> and <tt>:to</tt> options:
#
# should_change "Post.count", :from => 0, :to => 1
# should_change "@post.title", :from => "old", :to => "new"
#
# Combinations of <tt>:by</tt>, <tt>:from</tt>, and <tt>:to</tt> are allowed:
#
# should_change "@post.title" # => assert the value changed in some way
# should_change "@post.title", :from => "old" # => assert the value changed to anything other than "old"
# should_change "@post.title", :to => "new" # => assert the value changed from anything other than "new"
def should_change(expression, options = {})
by, from, to = get_options!([options], :by, :from, :to)
stmt = "change #{expression.inspect}"
stmt << " from #{from.inspect}" if from
stmt << " to #{to.inspect}" if to
stmt << " by #{by.inspect}" if by
expression_eval = lambda { eval(expression) }
before = lambda { @_before_should_change = expression_eval.bind(self).call }
should stmt, :before => before do
old_value = @_before_should_change
new_value = expression_eval.bind(self).call
assert_operator from, :===, old_value, "#{expression.inspect} did not originally match #{from.inspect}" if from
assert_not_equal old_value, new_value, "#{expression.inspect} did not change" unless by == 0
assert_operator to, :===, new_value, "#{expression.inspect} was not changed to match #{to.inspect}" if to
assert_equal old_value + by, new_value if by
end
end
# Macro that creates a test asserting no change between the return value
# of an expression that is run before and after the current setup block
# is run. This is the logical opposite of should_change.
#
# Example:
#
# context "Updating a post" do
# setup { @post.update_attributes(:title => "new") }
# should_not_change "Post.count"
# end
def should_not_change(expression)
expression_eval = lambda { eval(expression) }
before = lambda { @_before_should_not_change = expression_eval.bind(self).call }
should "not change #{expression.inspect}", :before => before do
new_value = expression_eval.bind(self).call
assert_equal @_before_should_not_change, new_value, "#{expression.inspect} changed"
end
end
private
include Shoulda::Private
end
end

View File

@@ -0,0 +1,20 @@
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
end
end

View File

@@ -0,0 +1,14 @@
# Stolen straight from ActiveSupport
class Proc #:nodoc:
def bind(object)
block, time = self, Time.now
(class << object; self end).class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
method = instance_method(method_name)
remove_method(method_name)
method
end.bind(object)
end
end

View File

@@ -0,0 +1,13 @@
require 'rubygems'
require 'active_support'
require 'shoulda'
require 'shoulda/active_record' if defined? ActiveRecord::Base
require 'shoulda/action_controller' if defined? ActionController::Base
require 'shoulda/action_view' if defined? ActionView::Base
require 'shoulda/action_mailer' if defined? ActionMailer::Base
if defined?(RAILS_ROOT)
# load in the 3rd party macros from vendorized plugins and gems
Shoulda.autoload_macros RAILS_ROOT, File.join("vendor", "{plugins,gems}", "*")
end

View File

@@ -0,0 +1,11 @@
require 'shoulda/active_record/matchers'
require 'shoulda/action_controller/matchers'
require 'active_support/test_case'
# :enddoc:
module ActiveSupport
class TestCase
include Shoulda::ActiveRecord::Matchers
include Shoulda::ActionController::Matchers
end
end

View File

@@ -0,0 +1,3 @@
Dir[File.join(File.dirname(__FILE__), 'tasks', '*.rake')].each do |f|
load f
end

View File

@@ -0,0 +1,29 @@
namespace :shoulda do
desc "List the names of the test methods in a specification like format"
task :list do
$LOAD_PATH.unshift("test")
require 'test/unit'
require 'rubygems'
require 'active_support'
# bug in test unit. Set to true to stop from running.
Test::Unit.run = true
test_files = Dir.glob(File.join('test', '**', '*_test.rb'))
test_files.each do |file|
load file
klass = File.basename(file, '.rb').classify
unless Object.const_defined?(klass.to_s)
puts "Skipping #{klass} because it doesn't map to a Class"
next
end
klass = klass.constantize
puts klass.name.gsub('Test', '')
test_methods = klass.instance_methods.grep(/^test/).map {|s| s.gsub(/^test: /, '')}.sort
test_methods.each {|m| puts " " + m }
end
end
end

View File

@@ -0,0 +1,28 @@
namespace :shoulda do
# From http://blog.internautdesign.com/2007/11/2/a-yaml_to_shoulda-rake-task
# David.Lowenfels@gmail.com
desc "Converts a YAML file (FILE=./path/to/yaml) into a Shoulda skeleton"
task :from_yaml do
require 'yaml'
def yaml_to_context(hash, indent = 0)
indent1 = ' ' * indent
indent2 = ' ' * (indent + 1)
hash.each_pair do |context, shoulds|
puts indent1 + "context \"#{context}\" do"
puts
shoulds.each do |should|
yaml_to_context( should, indent + 1 ) and next if should.is_a?( Hash )
puts indent2 + "should_eventually \"" + should.gsub(/^should +/,'') + "\" do"
puts indent2 + "end"
puts
end
puts indent1 + "end"
end
end
puts("Please pass in a FILE argument.") and exit unless ENV['FILE']
yaml_to_context( YAML.load_file( ENV['FILE'] ) )
end
end

View File

@@ -0,0 +1,19 @@
require 'shoulda/context'
require 'shoulda/proc_extensions'
require 'shoulda/assertions'
require 'shoulda/macros'
require 'shoulda/helpers'
require 'shoulda/autoload_macros'
require 'shoulda/rails' if defined? RAILS_ROOT
module Test # :nodoc: all
module Unit
class TestCase
extend Shoulda::ClassMethods
include Shoulda::Assertions
extend Shoulda::Macros
include Shoulda::Helpers
end
end
end

View File

@@ -0,0 +1,7 @@
if RAILS_ENV == 'test'
if defined? Spec
require 'shoulda/rspec'
else
require 'shoulda/rails'
end
end

View File

@@ -0,0 +1,36 @@
The Shoulda test suite (in particular - the tests that test shoulda)
Quick overview:
The test directory contains the following files and subdirectories:
* rails_root - contains the stripped down rails application that the tests run against. The rails root contains:
** the models, controllers, and views defined under app/
** the test.rb environment file
** a migration file for each model
** a shoulda initializer that simulates loading the plugin but without relying on vendor/plugins
* fixtures - contain the sample DB data for each model
* functional - controller tests for each of the controllers under rails_root/app
* unit - model tests for each of the models under rails_root/app
* other - tests for the shoulda contexts, should statements, and assertions
* test_helper.rb - responsible for initializing the test environment
** sets the rails_env to test
** sets the rails_root
** runs all the migrations against the in-memory sqlite3 db
** adds some magic to load the right fixture files
In order to add a new model (or controller) to the test suite:
* add that model to rails_root/app/models
* add a migration for that model
* add a fixture file
* add a test for that file under test/units
Dependencies:
* Rails gem installed in the host system
* A working sqlite3 installation.
If you have problems running these tests, please notify the mailing list: shoulda@googlegroups.com
- Tammer Saleh <tsaleh@thoughtbot.com>

View File

@@ -0,0 +1,34 @@
module Shoulda
class << self
attr_accessor :expected_exceptions
end
module ClassMethods
# Enables the core shoulda test suite to test for failure scenarios. For
# example, to ensure that a set of test macros should fail, do this:
#
# should_fail do
# should_validate_presence_of :comments
# should_not_allow_mass_assignment_of :name
# end
def should_fail(&block)
context "should fail when trying to run:" do
Shoulda.expected_exceptions = [Test::Unit::AssertionFailedError]
yield block
Shoulda.expected_exceptions = nil
end
end
end
class Context
# alias_method_chain hack to allow the should_fail macro to work
def should_with_failure_scenario(name, options = {}, &block)
if Shoulda.expected_exceptions
expected_exceptions = Shoulda.expected_exceptions
failure_block = lambda { assert_raise(*expected_exceptions, &block.bind(self)) }
end
should_without_failure_scenario(name, options, &(failure_block || block))
end
alias_method_chain :should, :failure_scenario
end
end

View File

@@ -0,0 +1,3 @@
first:
title: Home
addressable: first (User)

View File

@@ -0,0 +1,5 @@
first:
id: 1
title: My Cute Kitten!
body: This is totally a cute kitten
user_id: 1

View File

@@ -0,0 +1,9 @@
first:
id: 1
name: Stuff
second:
id: 2
name: Rails
third:
id: 3
name: Nothing

View File

@@ -0,0 +1,6 @@
first:
id: 1
name: Some dude
age: 2
email: none@none.com
ssn: 123456789

View File

@@ -0,0 +1,125 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'posts_controller'
# Re-raise errors caught by the controller.
class PostsController; def rescue_action(e) raise e end; end
class PostsControllerTest < Test::Unit::TestCase
fixtures :all
def setup
@controller = PostsController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@post = Post.find(:first)
end
# autodetects the :controller
should_route :get, '/posts', :action => :index
# explicitly specify :controller
should_route :post, '/posts', :controller => :posts, :action => :create
# non-string parameter
should_route :get, '/posts/1', :action => :show, :id => 1
# string-parameter
should_route :put, '/posts/1', :action => :update, :id => "1"
should_route :delete, '/posts/1', :action => :destroy, :id => 1
should_route :get, '/posts/new', :action => :new
# Test the nested routes
should_route :get, '/users/5/posts', :action => :index, :user_id => 5
should_route :post, '/users/5/posts', :action => :create, :user_id => 5
should_route :get, '/users/5/posts/1', :action => :show, :id => 1, :user_id => 5
should_route :delete, '/users/5/posts/1', :action => :destroy, :id => 1, :user_id => 5
should_route :get, '/users/5/posts/new', :action => :new, :user_id => 5
should_route :put, '/users/5/posts/1', :action => :update, :id => 1, :user_id => 5
context "Logged in" do
setup do
@request.session[:logged_in] = true
end
context "viewing posts for a user" do
setup do
get :index, :user_id => users(:first)
end
should_respond_with :success
should_assign_to :user, :class => User, :equals => 'users(:first)'
should_assign_to(:user) { users(:first) }
should_fail do
should_assign_to :user, :class => Post
end
should_fail do
should_assign_to :user, :equals => 'posts(:first)'
end
should_fail do
should_assign_to(:user) { posts(:first) }
end
should_assign_to :posts
should_not_assign_to :foo, :bar
should_render_page_with_metadata :description => /Posts/, :title => /index/
should_render_page_with_metadata :keywords => "posts"
end
context "viewing posts for a user with rss format" do
setup do
get :index, :user_id => users(:first), :format => 'rss'
@user = users(:first)
end
should_respond_with :success
should_respond_with_content_type 'application/rss+xml'
should_respond_with_content_type :rss
should_respond_with_content_type /rss/
context "deprecated" do # to avoid redefining a test
should_return_from_session :special, "'$2 off your next purchase'"
end
should_fail do
should_return_from_session :special, "'not special'"
end
should_set_session(:mischief) { nil }
should_return_from_session :malarky, "nil"
should_set_session :special, "'$2 off your next purchase'"
should_set_session :special_user_id, '@user.id'
context "with a block" do
should_set_session(:special_user_id) { @user.id }
end
should_fail do # to avoid redefining a test
should_set_session(:special_user_id) { 'value' }
end
should_assign_to :user, :posts
should_not_assign_to :foo, :bar
end
context "viewing a post on GET to #show" do
setup { get :show, :user_id => users(:first), :id => posts(:first) }
should_render_with_layout 'wide'
context "with a symbol" do # to avoid redefining a test
should_render_with_layout :wide
end
should_assign_to :false_flag
end
context "on GET to #new" do
setup { get :new, :user_id => users(:first) }
should_render_without_layout
end
context "on POST to #create" do
setup do
post :create, :user_id => users(:first),
:post => { :title => "first post",
:body => 'blah blah blah' }
end
should_redirect_to 'user_post_url(@post.user, @post)'
should_redirect_to('the created post') { user_post_url(users(:first),
assigns(:post)) }
should_fail do
should_redirect_to 'user_posts_url(@post.user)'
end
should_fail do
should_redirect_to('elsewhere') { user_posts_url(users(:first)) }
end
end
end
end

View File

@@ -0,0 +1,19 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'users_controller'
# Re-raise errors caught by the controller.
class UsersController; def rescue_action(e) raise e end; end
class UsersControllerTest < Test::Unit::TestCase
fixtures :all
def setup
@controller = UsersController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@user = User.find(:first)
end
should_filter_params :ssn
end

View File

@@ -0,0 +1,68 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class AllowMassAssignmentOfMatcherTest < Test::Unit::TestCase # :nodoc:
context "an attribute that is blacklisted from mass-assignment" do
setup do
define_model :example, :attr => :string do
attr_protected :attr
end
@model = Example.new
end
should "reject being mass-assignable" do
assert_rejects allow_mass_assignment_of(:attr), @model
end
end
context "an attribute that is not whitelisted for mass-assignment" do
setup do
define_model :example, :attr => :string, :other => :string do
attr_accessible :other
end
@model = Example.new
end
should "reject being mass-assignable" do
assert_rejects allow_mass_assignment_of(:attr), @model
end
end
context "an attribute that is whitelisted for mass-assignment" do
setup do
define_model :example, :attr => :string do
attr_accessible :attr
end
@model = Example.new
end
should "accept being mass-assignable" do
assert_accepts allow_mass_assignment_of(:attr), @model
end
end
context "an attribute not included in the mass-assignment blacklist" do
setup do
define_model :example, :attr => :string, :other => :string do
attr_protected :other
end
@model = Example.new
end
should "accept being mass-assignable" do
assert_accepts allow_mass_assignment_of(:attr), @model
end
end
context "an attribute on a class with no protected attributes" do
setup do
define_model :example, :attr => :string
@model = Example.new
end
should "accept being mass-assignable" do
assert_accepts allow_mass_assignment_of(:attr), @model
end
end
end

View File

@@ -0,0 +1,41 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class AllowValueMatcherTest < Test::Unit::TestCase # :nodoc:
context "an attribute with a format validation" do
setup do
define_model :example, :attr => :string do
validates_format_of :attr, :with => /abc/
end
@model = Example.new
end
should "allow a good value" do
assert_accepts allow_value("abcde").for(:attr), @model
end
should "not allow a bad value" do
assert_rejects allow_value("xyz").for(:attr), @model
end
end
context "an attribute with a format validation and a custom message" do
setup do
define_model :example, :attr => :string do
validates_format_of :attr, :with => /abc/, :message => 'bad value'
end
@model = Example.new
end
should "allow a good value" do
assert_accepts allow_value('abcde').for(:attr).with_message(/bad/),
@model
end
should "not allow a bad value" do
assert_rejects allow_value('xyz').for(:attr).with_message(/bad/),
@model
end
end
end

View File

@@ -0,0 +1,258 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class AssociationMatcherTest < Test::Unit::TestCase # :nodoc:
context "belong_to" do
setup do
@matcher = belong_to(:parent)
end
should "accept a good association with the default foreign key" do
define_model :parent
define_model :child, :parent_id => :integer do
belongs_to :parent
end
assert_accepts @matcher, Child.new
end
should "reject a nonexistent association" do
define_model :child
assert_rejects @matcher, Child.new
end
should "reject an association of the wrong type" do
define_model :parent, :child_id => :integer
child_class = define_model :child do
has_one :parent
end
assert_rejects @matcher, Child.new
end
should "reject an association that has a nonexistent foreign key" do
define_model :parent
define_model :child do
belongs_to :parent
end
assert_rejects @matcher, Child.new
end
should "accept an association with an existing custom foreign key" do
define_model :parent
define_model :child, :guardian_id => :integer do
belongs_to :parent, :foreign_key => 'guardian_id'
end
assert_accepts @matcher, Child.new
end
should "accept a polymorphic association" do
define_model :child, :parent_type => :string,
:parent_id => :integer do
belongs_to :parent, :polymorphic => true
end
assert_accepts @matcher, Child.new
end
should "accept an association with a valid :dependent option" do
define_model :parent
define_model :child, :parent_id => :integer do
belongs_to :parent, :dependent => :destroy
end
assert_accepts @matcher.dependent(:destroy), Child.new
end
should "reject an association with a bad :dependent option" do
define_model :parent
define_model :child, :parent_id => :integer do
belongs_to :parent
end
assert_rejects @matcher.dependent(:destroy), Child.new
end
end
context "have_many" do
setup do
@matcher = have_many(:children)
end
should "accept a valid association without any options" do
define_model :child, :parent_id => :integer
define_model :parent do
has_many :children
end
assert_accepts @matcher, Parent.new
end
should "accept a valid association with a :through option" do
define_model :child
define_model :conception, :child_id => :integer,
:parent_id => :integer do
belongs_to :child
end
define_model :parent do
has_many :conceptions
has_many :children, :through => :conceptions
end
assert_accepts @matcher, Parent.new
end
should "accept a valid association with an :as option" do
define_model :child, :guardian_type => :string,
:guardian_id => :integer
define_model :parent do
has_many :children, :as => :guardian
end
assert_accepts @matcher, Parent.new
end
should "reject an association that has a nonexistent foreign key" do
define_model :child
define_model :parent do
has_many :children
end
assert_rejects @matcher, Parent.new
end
should "reject an association with a bad :as option" do
define_model :child, :caretaker_type => :string,
:caretaker_id => :integer
define_model :parent do
has_many :children, :as => :guardian
end
assert_rejects @matcher, Parent.new
end
should "reject an association that has a bad :through option" do
define_model :child, :parent_id => :integer
define_model :parent do
has_many :children
end
assert_rejects @matcher.through(:conceptions), Parent.new
end
should "reject an association that has the wrong :through option" do
define_model :child
define_model :conception, :child_id => :integer,
:parent_id => :integer do
belongs_to :child
end
define_model :parent do
has_many :conceptions
has_many :children, :through => :conceptions
end
assert_rejects @matcher.through(:relationships), Parent.new
end
should "accept an association with a valid :dependent option" do
define_model :child, :parent_id => :integer
define_model :parent do
has_many :children, :dependent => :destroy
end
assert_accepts @matcher.dependent(:destroy), Parent.new
end
should "reject an association with a bad :dependent option" do
define_model :child, :parent_id => :integer
define_model :parent do
has_many :children
end
assert_rejects @matcher.dependent(:destroy), Parent.new
end
end
context "have_one" do
setup do
@matcher = have_one(:profile)
end
should "accept a valid association without any options" do
define_model :profile, :person_id => :integer
define_model :person do
has_one :profile
end
assert_accepts @matcher, Person.new
end
should "accept a valid association with an :as option" do
define_model :profile, :profilable_id => :integer,
:profilable_type => :string
define_model :person do
has_one :profile, :as => :profilable
end
assert_accepts @matcher, Person.new
end
should "reject an association that has a nonexistent foreign key" do
define_model :profile
define_model :person do
has_one :profile
end
assert_rejects @matcher, Person.new
end
should "reject an association with a bad :as option" do
define_model :profile, :profilable_id => :integer,
:profilable_type => :string
define_model :person do
has_one :profile, :as => :describable
end
assert_rejects @matcher, Person.new
end
should "accept an association with a valid :dependent option" do
define_model :profile, :person_id => :integer
define_model :person do
has_one :profile, :dependent => :destroy
end
assert_accepts @matcher.dependent(:destroy), Person.new
end
should "reject an association with a bad :dependent option" do
define_model :profile, :person_id => :integer
define_model :person do
has_one :profile
end
assert_rejects @matcher.dependent(:destroy), Person.new
end
end
context "have_and_belong_to_many" do
setup do
@matcher = have_and_belong_to_many(:relatives)
end
should "accept a valid association" do
define_model :relatives
define_model :person do
has_and_belongs_to_many :relatives
end
define_model :people_relative, :person_id => :integer,
:relative_id => :integer
assert_accepts @matcher, Person.new
end
should "reject a nonexistent association" do
define_model :relatives
define_model :person
define_model :people_relative, :person_id => :integer,
:relative_id => :integer
assert_rejects @matcher, Person.new
end
should "reject an association with a nonexistent join table" do
define_model :relatives
define_model :person do
has_and_belongs_to_many :relatives
end
assert_rejects @matcher, Person.new
end
should "reject an association of the wrong type" do
define_model :relatives, :person_id => :integer
define_model :person do
has_many :relatives
end
assert_rejects @matcher, Person.new
end
end
end

View File

@@ -0,0 +1,80 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class EnsureInclusionOfMatcherTest < Test::Unit::TestCase # :nodoc:
context "an attribute which must be included in a range" do
setup do
@model = define_model(:example, :attr => :integer) do
validates_inclusion_of :attr, :in => 2..5
end.new
end
should "accept ensuring the correct range" do
assert_accepts ensure_inclusion_of(:attr).in_range(2..5), @model
end
should "reject ensuring a lower minimum value" do
assert_rejects ensure_inclusion_of(:attr).in_range(1..5), @model
end
should "reject ensuring a higher minimum value" do
assert_rejects ensure_inclusion_of(:attr).in_range(3..5), @model
end
should "reject ensuring a lower maximum value" do
assert_rejects ensure_inclusion_of(:attr).in_range(2..4), @model
end
should "reject ensuring a higher maximum value" do
assert_rejects ensure_inclusion_of(:attr).in_range(2..6), @model
end
should "not override the default message with a blank" do
assert_accepts ensure_inclusion_of(:attr).
in_range(2..5).
with_message(nil),
@model
end
end
context "an attribute with a custom ranged value validation" do
setup do
@model = define_model(:example, :attr => :string) do
validates_inclusion_of :attr, :in => 2..4, :message => 'not good'
end.new
end
should "accept ensuring the correct range" do
assert_accepts ensure_inclusion_of(:attr).
in_range(2..4).
with_message(/not good/),
@model
end
end
context "an attribute with custom range validations" do
setup do
define_model :example, :attr => :integer do
def validate
if attr < 2
errors.add(:attr, 'too low')
elsif attr > 5
errors.add(:attr, 'too high')
end
end
end
@model = Example.new
end
should "accept ensuring the correct range and messages" do
assert_accepts ensure_inclusion_of(:attr).
in_range(2..5).
with_low_message(/low/).
with_high_message(/high/),
@model
end
end
end

View File

@@ -0,0 +1,158 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class EnsureLengthOfMatcher < Test::Unit::TestCase # :nodoc:
context "an attribute with a non-zero minimum length validation" do
setup do
@model = define_model(:example, :attr => :string) do
validates_length_of :attr, :minimum => 4
end.new
end
should "accept ensuring the correct minimum length" do
assert_accepts ensure_length_of(:attr).is_at_least(4), @model
end
should "reject ensuring a lower minimum length with any message" do
assert_rejects ensure_length_of(:attr).
is_at_least(3).
with_short_message(/.*/),
@model
end
should "reject ensuring a higher minimum length with any message" do
assert_rejects ensure_length_of(:attr).
is_at_least(5).
with_short_message(/.*/),
@model
end
should "not override the default message with a blank" do
assert_accepts ensure_length_of(:attr).
is_at_least(4).
with_short_message(nil),
@model
end
end
context "an attribute with a minimum length validation of 0" do
setup do
@model = define_model(:example, :attr => :string) do
validates_length_of :attr, :minimum => 0
end.new
end
should "accept ensuring the correct minimum length" do
assert_accepts ensure_length_of(:attr).is_at_least(0), @model
end
end
context "an attribute with a maximum length" do
setup do
@model = define_model(:example, :attr => :string) do
validates_length_of :attr, :maximum => 4
end.new
end
should "accept ensuring the correct maximum length" do
assert_accepts ensure_length_of(:attr).is_at_most(4), @model
end
should "reject ensuring a lower maximum length with any message" do
assert_rejects ensure_length_of(:attr).
is_at_most(3).
with_long_message(/.*/),
@model
end
should "reject ensuring a higher maximum length with any message" do
assert_rejects ensure_length_of(:attr).
is_at_most(5).
with_long_message(/.*/),
@model
end
should "not override the default message with a blank" do
assert_accepts ensure_length_of(:attr).
is_at_most(4).
with_long_message(nil),
@model
end
end
context "an attribute with a required exact length" do
setup do
@model = define_model(:example, :attr => :string) do
validates_length_of :attr, :is => 4
end.new
end
should "accept ensuring the correct length" do
assert_accepts ensure_length_of(:attr).is_equal_to(4), @model
end
should "reject ensuring a lower maximum length with any message" do
assert_rejects ensure_length_of(:attr).
is_equal_to(3).
with_message(/.*/),
@model
end
should "reject ensuring a higher maximum length with any message" do
assert_rejects ensure_length_of(:attr).
is_equal_to(5).
with_message(/.*/),
@model
end
should "not override the default message with a blank" do
assert_accepts ensure_length_of(:attr).
is_equal_to(4).
with_message(nil),
@model
end
end
context "an attribute with a custom minimum length validation" do
setup do
@model = define_model(:example, :attr => :string) do
validates_length_of :attr, :minimum => 4, :too_short => 'short'
end.new
end
should "accept ensuring the correct minimum length" do
assert_accepts ensure_length_of(:attr).
is_at_least(4).
with_short_message(/short/),
@model
end
end
context "an attribute with a custom maximum length validation" do
setup do
@model = define_model(:example, :attr => :string) do
validates_length_of :attr, :maximum => 4, :too_long => 'long'
end.new
end
should "accept ensuring the correct minimum length" do
assert_accepts ensure_length_of(:attr).
is_at_most(4).
with_long_message(/long/),
@model
end
end
context "an attribute without a length validation" do
setup do
@model = define_model(:example, :attr => :string).new
end
should "reject ensuring a minimum length" do
assert_rejects ensure_length_of(:attr).is_at_least(1), @model
end
end
end

View File

@@ -0,0 +1,169 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class HaveDbColumnMatcherTest < Test::Unit::TestCase # :nodoc:
context "have_db_column" do
setup do
@matcher = have_db_column(:nickname)
end
should "accept an existing database column" do
create_table 'superheros' do |table|
table.string :nickname
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a nonexistent database column" do
define_model :superhero
assert_rejects @matcher, Superhero.new
end
end
context "have_db_column of type string" do
setup do
@matcher = have_db_column(:nickname).of_type(:string)
end
should "accept a column of correct type" do
create_table 'superheros' do |table|
table.string :nickname
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a nonexistent database column" do
define_model :superhero
assert_rejects @matcher, Superhero.new
end
should "reject a column of wrong type" do
create_table 'superheros' do |table|
table.integer :nickname
end
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
context "have_db_column with precision option" do
setup do
@matcher = have_db_column(:salary).with_options(:precision => 5)
end
should "accept a column of correct precision" do
create_table 'superheros' do |table|
table.decimal :salary, :precision => 5
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a column of wrong precision" do
create_table 'superheros' do |table|
table.decimal :salary, :precision => 15
end
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
context "have_db_column with limit option" do
setup do
@matcher = have_db_column(:email).
of_type(:string).
with_options(:limit => 255)
end
should "accept a column of correct limit" do
create_table 'superheros' do |table|
table.string :email, :limit => 255
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a column of wrong limit" do
create_table 'superheros' do |table|
table.string :email, :limit => 500
end
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
context "have_db_column with default option" do
setup do
@matcher = have_db_column(:admin).
of_type(:boolean).
with_options(:default => false)
end
should "accept a column of correct default" do
create_table 'superheros' do |table|
table.boolean :admin, :default => false
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a column of wrong default" do
create_table 'superheros' do |table|
table.boolean :admin, :default => true
end
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
context "have_db_column with null option" do
setup do
@matcher = have_db_column(:admin).
of_type(:boolean).
with_options(:null => false)
end
should "accept a column of correct null" do
create_table 'superheros' do |table|
table.boolean :admin, :null => false
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a column of wrong null" do
create_table 'superheros' do |table|
table.boolean :admin, :null => true
end
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
context "have_db_column with scale option" do
setup do
@matcher = have_db_column(:salary).
of_type(:decimal).
with_options(:scale => 2)
end
should "accept a column of correct scale" do
create_table 'superheros' do |table|
table.decimal :salary, :precision => 10, :scale => 2
end
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a column of wrong scale" do
create_table 'superheros' do |table|
table.decimal :salary, :precision => 10, :scale => 4
end
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
end

View File

@@ -0,0 +1,74 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class HaveIndexMatcherTest < Test::Unit::TestCase # :nodoc:
context "have_index" do
setup do
@matcher = have_index(:age)
end
should "accept an existing index" do
db_connection = create_table 'superheros' do |table|
table.integer :age
end
db_connection.add_index :superheros, :age
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject a nonexistent index" do
define_model :superhero
assert_rejects @matcher, Superhero.new
end
end
context "have_index with unique option" do
setup do
@matcher = have_index(:ssn).unique(true)
end
should "accept an index of correct unique" do
db_connection = create_table 'superheros' do |table|
table.integer :ssn
end
db_connection.add_index :superheros, :ssn, :unique => true
define_model_class 'Superhero'
assert_accepts @matcher, Superhero.new
end
should "reject an index of wrong unique" do
db_connection = create_table 'superheros' do |table|
table.integer :ssn
end
db_connection.add_index :superheros, :ssn, :unique => false
define_model_class 'Superhero'
assert_rejects @matcher, Superhero.new
end
end
context "have_index on multiple columns" do
setup do
@matcher = have_index([:geocodable_type, :geocodable_id])
end
should "accept an existing index" do
db_connection = create_table 'geocodings' do |table|
table.integer :geocodable_id
table.string :geocodable_type
end
db_connection.add_index :geocodings, [:geocodable_type, :geocodable_id]
define_model_class 'Geocoding'
assert_accepts @matcher, Geocoding.new
end
should "reject a nonexistant index" do
db_connection = create_table 'geocodings' do |table|
table.integer :geocodable_id
table.string :geocodable_type
end
define_model_class 'Geocoding'
assert_rejects @matcher, Geocoding.new
end
end
end

View File

@@ -0,0 +1,65 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class HaveNamedScopeMatcherTest < Test::Unit::TestCase # :nodoc:
context "an attribute with a named scope" do
setup do
define_model :example, :attr => :string do
named_scope :xyz, lambda {|n|
{ :order => :attr }
}
end
@model = Example.new
end
should "accept having a scope with the correct signature" do
assert_accepts have_named_scope("xyz(1)"), @model
end
should "accept having a scope with the correct signature and find options" do
assert_accepts have_named_scope("xyz(1)").finding(:order => :attr), @model
end
should "reject having a scope with incorrect find options" do
assert_rejects have_named_scope("xyz(1)").
finding(:order => 'attr DESC'),
@model
end
should "reject having a scope with another name" do
assert_rejects have_named_scope("abc(1)"), @model
end
end
should "evaluate the scope in the correct context" do
define_model :example, :attr => :string do
named_scope :xyz, lambda {|n|
{ :order => n }
}
end
model = Example.new
@order = :attr
assert_accepts have_named_scope("xyz(@order)").
finding(:order => @order).
in_context(self),
model
end
context "a method that does not return a scope" do
setup do
klass = Class.new
klass.class_eval do
def self.xyz
'xyz'
end
end
@model = klass.new
end
should "reject having a named scope with that name" do
assert_rejects have_named_scope(:xyz), @model
end
end
end

View File

@@ -0,0 +1,29 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class HaveReadonlyAttributesMatcherTest < Test::Unit::TestCase # :nodoc:
context "an attribute that cannot be set after being saved" do
setup do
define_model :example, :attr => :string do
attr_readonly :attr
end
@model = Example.new
end
should "accept being read-only" do
assert_accepts have_readonly_attribute(:attr), @model
end
end
context "an attribute that can be set after being saved" do
setup do
define_model :example, :attr => :string
@model = Example.new
end
should "accept being read-only" do
assert_rejects have_readonly_attribute(:attr), @model
end
end
end

View File

@@ -0,0 +1,44 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ValidateAcceptanceOfMatcherTest < Test::Unit::TestCase # :nodoc:
context "an attribute which must be accepted" do
setup do
@model = define_model(:example) do
validates_acceptance_of :attr
end.new
end
should "require that attribute to be accepted" do
assert_accepts validate_acceptance_of(:attr), @model
end
should "not overwrite the default message with nil" do
assert_accepts validate_acceptance_of(:attr).with_message(nil), @model
end
end
context "an attribute that does not need to be accepted" do
setup do
@model = define_model(:example, :attr => :string).new
end
should "not require that attribute to be accepted" do
assert_rejects validate_acceptance_of(:attr), @model
end
end
context "an attribute which must be accepted with a custom message" do
setup do
@model = define_model(:example) do
validates_acceptance_of :attr, :message => 'custom'
end.new
end
should "require that attribute to be accepted with that message" do
assert_accepts validate_acceptance_of(:attr).with_message(/custom/),
@model
end
end
end

View File

@@ -0,0 +1,52 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ValidateNumericalityOfMatcherTest < Test::Unit::TestCase # :nodoc:
context "a numeric attribute" do
setup do
define_model :example, :attr => :string do
validates_numericality_of :attr
end
@model = Example.new
end
should "only allow numeric values for that attribute" do
assert_accepts validate_numericality_of(:attr), @model
end
should "not override the default message with a blank" do
assert_accepts validate_numericality_of(:attr).with_message(nil),
@model
end
end
context "a numeric attribute with a custom validation message" do
setup do
define_model :example, :attr => :string do
validates_numericality_of :attr, :message => 'custom'
end
@model = Example.new
end
should "only allow numeric values for that attribute with that message" do
assert_accepts validate_numericality_of(:attr).
with_message(/custom/),
@model
end
should "not allow numeric values for that attribute with another message" do
assert_rejects validate_numericality_of(:attr), @model
end
end
context "a non-numeric attribute" do
setup do
@model = define_model(:example, :attr => :string).new
end
should "not only allow numeric values for that attribute" do
assert_rejects validate_numericality_of(:attr), @model
end
end
end

View File

@@ -0,0 +1,86 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ValidatePresenceOfMatcherTest < Test::Unit::TestCase # :nodoc:
context "a required attribute" do
setup do
define_model :example, :attr => :string do
validates_presence_of :attr
end
@model = Example.new
end
should "require a value" do
assert_accepts validate_presence_of(:attr), @model
end
should "not override the default message with a blank" do
assert_accepts validate_presence_of(:attr).with_message(nil), @model
end
end
context "an optional attribute" do
setup do
@model = define_model(:example, :attr => :string).new
end
should "not require a value" do
assert_rejects validate_presence_of(:attr), @model
end
end
context "a required has_many association" do
setup do
define_model :child
@model = define_model :parent do
has_many :children
validates_presence_of :children
end.new
end
should "require the attribute to be set" do
assert_accepts validate_presence_of(:children), @model
end
end
context "an optional has_many association" do
setup do
define_model :child
@model = define_model :parent do
has_many :children
end.new
end
should "not require the attribute to be set" do
assert_rejects validate_presence_of(:children), @model
end
end
context "a required has_and_belongs_to_many association" do
setup do
define_model :child
@model = define_model :parent do
has_and_belongs_to_many :children
validates_presence_of :children
end.new
end
should "require the attribute to be set" do
assert_accepts validate_presence_of(:children), @model
end
end
context "an optional has_and_belongs_to_many association" do
setup do
define_model :child
@model = define_model :parent do
has_and_belongs_to_many :children
end.new
end
should "not require the attribute to be set" do
assert_rejects validate_presence_of(:children), @model
end
end
end

View File

@@ -0,0 +1,147 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class ValidateUniquenessOfMatcherTest < Test::Unit::TestCase # :nodoc:
context "a unique attribute" do
setup do
@model = define_model(:example, :attr => :string,
:other => :integer) do
validates_uniqueness_of :attr
end.new
end
context "with an existing value" do
setup do
@existing = Example.create!(:attr => 'value', :other => 1)
end
should "require a unique value for that attribute" do
assert_accepts validate_uniqueness_of(:attr), @model
end
should "pass when the subject is an existing record" do
assert_accepts validate_uniqueness_of(:attr), @existing
end
should "fail when a scope is specified" do
assert_rejects validate_uniqueness_of(:attr).scoped_to(:other), @model
end
end
context "without an existing value" do
setup do
assert_nil Example.find(:first)
@matcher = validate_uniqueness_of(:attr)
end
should "fail to require a unique value" do
assert_rejects @matcher, @model
end
should "alert the tester that an existing value is not present" do
@matcher.matches?(@model)
assert @matcher.negative_failure_message =~ /^Can't find first .*/
end
end
end
context "a unique attribute with a custom error and an existing value" do
setup do
@model = define_model(:example, :attr => :string) do
validates_uniqueness_of :attr, :message => 'Bad value'
end.new
Example.create!
end
should "fail when checking the default message" do
assert_rejects validate_uniqueness_of(:attr), @model
end
should "fail when checking a message that doesn't match" do
assert_rejects validate_uniqueness_of(:attr).with_message(/abc/i), @model
end
should "pass when checking a message that matches" do
assert_accepts validate_uniqueness_of(:attr).with_message(/bad/i), @model
end
end
context "a scoped unique attribute with an existing value" do
setup do
@model = define_model(:example, :attr => :string,
:scope1 => :integer,
:scope2 => :integer) do
validates_uniqueness_of :attr, :scope => [:scope1, :scope2]
end.new
@existing = Example.create!(:attr => 'value', :scope1 => 1, :scope2 => 2)
end
should "pass when the correct scope is specified" do
assert_accepts validate_uniqueness_of(:attr).scoped_to(:scope1, :scope2),
@model
end
should "pass when the subject is an existing record" do
assert_accepts validate_uniqueness_of(:attr).scoped_to(:scope1, :scope2),
@existing
end
should "fail when a different scope is specified" do
assert_rejects validate_uniqueness_of(:attr).scoped_to(:scope1), @model
end
should "fail when no scope is specified" do
assert_rejects validate_uniqueness_of(:attr), @model
end
should "fail when a non-existent attribute is specified as a scope" do
assert_rejects validate_uniqueness_of(:attr).scoped_to(:fake), @model
end
end
context "a non-unique attribute with an existing value" do
setup do
@model = define_model(:example, :attr => :string).new
Example.create!(:attr => 'value')
end
should "not require a unique value for that attribute" do
assert_rejects validate_uniqueness_of(:attr), @model
end
end
context "a case sensitive unique attribute with an existing value" do
setup do
@model = define_model(:example, :attr => :string) do
validates_uniqueness_of :attr, :case_sensitive => true
end.new
Example.create!(:attr => 'value')
end
should "not require a unique, case-insensitive value for that attribute" do
assert_rejects validate_uniqueness_of(:attr).case_insensitive, @model
end
should "require a unique, case-sensitive value for that attribute" do
assert_accepts validate_uniqueness_of(:attr), @model
end
end
context "a case sensitive unique integer attribute with an existing value" do
setup do
@model = define_model(:example, :attr => :integer) do
validates_uniqueness_of :attr, :case_sensitive => true
end.new
Example.create!(:attr => 'value')
end
should "require a unique, case-insensitive value for that attribute" do
assert_accepts validate_uniqueness_of(:attr).case_insensitive, @model
end
should "require a unique, case-sensitive value for that attribute" do
assert_accepts validate_uniqueness_of(:attr), @model
end
end
end

View File

@@ -0,0 +1,35 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class AssignToMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller that assigns to an instance variable" do
setup do
@controller = build_response { @var = 'value' }
end
should "accept assigning to that variable" do
assert_accepts assign_to(:var), @controller
end
should "accept assigning to that variable with the correct class" do
assert_accepts assign_to(:var).with_kind_of(String), @controller
end
should "reject assigning to that variable with another class" do
assert_rejects assign_to(:var).with_kind_of(Fixnum), @controller
end
should "accept assigning the correct value to that variable" do
assert_accepts assign_to(:var).with('value'), @controller
end
should "reject assigning another value to that variable" do
assert_rejects assign_to(:var).with('other'), @controller
end
should "reject assigning to another variable" do
assert_rejects assign_to(:other), @controller
end
end
end

View File

@@ -0,0 +1,32 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class FilterParamMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller that filters no parameters" do
setup do
@controller = define_controller(:examples).new
end
should "reject filtering any parameter" do
assert_rejects filter_param(:any), @controller
end
end
context "a controller that filters a parameter" do
setup do
@controller = define_controller :examples do
filter_parameter_logging :password
end.new
end
should "accept filtering that parameter" do
assert_accepts filter_param(:password), @controller
end
should "reject filtering another parameter" do
assert_rejects filter_param(:other), @controller
end
end
end

View File

@@ -0,0 +1,33 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class RenderWithLayoutMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller that renders with a layout" do
setup do
@controller = build_response { render :layout => 'wide' }
end
should "accept rendering with any layout" do
assert_accepts render_with_layout, @controller
end
should "accept rendering with that layout" do
assert_accepts render_with_layout(:wide), @controller
end
should "reject rendering with another layout" do
assert_rejects render_with_layout(:other), @controller
end
end
context "a controller that renders without a layout" do
setup do
@controller = build_response { render :layout => false }
end
should "reject rendering with a layout" do
assert_rejects render_with_layout, @controller
end
end
end

View File

@@ -0,0 +1,27 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class RespondWithContentTypeMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller responding with content type :xml" do
setup do
@controller = build_response { render :xml => { :user => "thoughtbot" }.to_xml }
end
should "accept responding with content type :xml" do
assert_accepts respond_with_content_type(:xml), @controller
end
should "accept responding with content type 'application/xml'" do
assert_accepts respond_with_content_type('application/xml'), @controller
end
should "accept responding with content type /xml/" do
assert_accepts respond_with_content_type(/xml/), @controller
end
should "reject responding with another content type" do
assert_rejects respond_with_content_type(:json), @controller
end
end
end

View File

@@ -0,0 +1,106 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class RespondWithMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller responding with success" do
setup do
@controller = build_response { render :text => "text", :status => 200 }
end
should "accept responding with 200" do
assert_accepts respond_with(200), @controller
end
should "accept responding with :success" do
assert_accepts respond_with(:success), @controller
end
should "reject responding with another status" do
assert_rejects respond_with(:error), @controller
end
end
context "a controller responding with redirect" do
setup do
@controller = build_response { render :text => "text", :status => 301 }
end
should "accept responding with 301" do
assert_accepts respond_with(301), @controller
end
should "accept responding with :redirect" do
assert_accepts respond_with(:redirect), @controller
end
should "reject responding with another status" do
assert_rejects respond_with(:error), @controller
end
end
context "a controller responding with missing" do
setup do
@controller = build_response { render :text => "text", :status => 404 }
end
should "accept responding with 404" do
assert_accepts respond_with(404), @controller
end
should "accept responding with :missing" do
assert_accepts respond_with(:missing), @controller
end
should "reject responding with another status" do
assert_rejects respond_with(:success), @controller
end
end
context "a controller responding with error" do
setup do
@controller = build_response { render :text => "text", :status => 500 }
end
should "accept responding with 500" do
assert_accepts respond_with(500), @controller
end
should "accept responding with :error" do
assert_accepts respond_with(:error), @controller
end
should "reject responding with another status" do
assert_rejects respond_with(:success), @controller
end
end
context "a controller responding with not implemented" do
setup do
@controller = build_response { render :text => "text", :status => 501 }
end
should "accept responding with 501" do
assert_accepts respond_with(501), @controller
end
should "accept responding with :not_implemented" do
assert_accepts respond_with(:not_implemented), @controller
end
should "reject responding with another status" do
assert_rejects respond_with(:success), @controller
end
end
context "a controller raising an error" do
setup do
@controller = build_response { raise RailsError }
end
should "reject responding with any status" do
assert_rejects respond_with(:success), @controller
end
end
end

View File

@@ -0,0 +1,58 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class RouteToMatcherTest < Test::Unit::TestCase # :nodoc:
context "given a controller with a defined route" do
setup do
@controller = define_controller('Examples').new
define_routes do |map|
map.connect 'examples/:id', :controller => 'examples',
:action => 'example'
end
end
should "accept routing the correct path to the correct parameters" do
assert_accepts route(:get, '/examples/1').
to(:action => 'example', :id => '1'),
@controller
end
should "accept a symbol controller" do
assert_accepts route(:get, '/examples/1').
to(:controller => :examples,
:action => 'example',
:id => '1'),
self
end
should "accept a symbol action" do
assert_accepts route(:get, '/examples/1').
to(:action => :example, :id => '1'),
@controller
end
should "accept a non-string parameter" do
assert_accepts route(:get, '/examples/1').
to(:action => 'example', :id => 1),
@controller
end
should "reject an undefined route" do
assert_rejects route(:get, '/bad_route').to(:var => 'value'), @controller
end
should "reject a route for another controller" do
@other = define_controller('Other').new
assert_rejects route(:get, '/examples/1').
to(:action => 'example', :id => '1'),
@other
end
should "reject a route for different parameters" do
assert_rejects route(:get, '/examples/1').
to(:action => 'other', :id => '1'),
@controller
end
end
end

View File

@@ -0,0 +1,31 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class SetSessionMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller that sets a session variable" do
setup do
@controller = build_response { session[:var] = 'value' }
end
should "accept assigning to that variable" do
assert_accepts set_session(:var), @controller
end
should "accept assigning the correct value to that variable" do
assert_accepts set_session(:var).to('value'), @controller
end
should "reject assigning another value to that variable" do
assert_rejects set_session(:var).to('other'), @controller
end
should "reject assigning to another variable" do
assert_rejects set_session(:other), @controller
end
should "accept assigning nil to another variable" do
assert_accepts set_session(:other).to(nil), @controller
end
end
end

View File

@@ -0,0 +1,41 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class SetTheFlashMatcherTest < Test::Unit::TestCase # :nodoc:
context "a controller that sets a flash message" do
setup do
@controller = build_response { flash[:notice] = 'value' }
end
should "accept setting any flash message" do
assert_accepts set_the_flash, @controller
end
should "accept setting the exact flash message" do
assert_accepts set_the_flash.to('value'), @controller
end
should "accept setting a matched flash message" do
assert_accepts set_the_flash.to(/value/), @controller
end
should "reject setting a different flash message" do
assert_rejects set_the_flash.to('other'), @controller
end
should "reject setting a different pattern" do
assert_rejects set_the_flash.to(/other/), @controller
end
end
context "a controller that doesn't set a flash message" do
setup do
@controller = build_response
end
should "reject setting any flash message" do
assert_rejects set_the_flash, @controller
end
end
end

View File

@@ -0,0 +1,106 @@
class Test::Unit::TestCase
def create_table(table_name, &block)
connection = ActiveRecord::Base.connection
begin
connection.execute("DROP TABLE IF EXISTS #{table_name}")
connection.create_table(table_name, &block)
@created_tables ||= []
@created_tables << table_name
connection
rescue Exception => e
connection.execute("DROP TABLE IF EXISTS #{table_name}")
raise e
end
end
def define_constant(class_name, base, &block)
class_name = class_name.to_s.camelize
klass = Class.new(base)
Object.const_set(class_name, klass)
klass.class_eval(&block) if block_given?
@defined_constants ||= []
@defined_constants << class_name
klass
end
def define_model_class(class_name, &block)
define_constant(class_name, ActiveRecord::Base, &block)
end
def define_model(name, columns = {}, &block)
class_name = name.to_s.pluralize.classify
table_name = class_name.tableize
create_table(table_name) do |table|
columns.each do |name, type|
table.column name, type
end
end
define_model_class(class_name, &block)
end
def define_controller(class_name, &block)
class_name = class_name.to_s
class_name << 'Controller' unless class_name =~ /Controller$/
define_constant(class_name, ActionController::Base, &block)
end
def define_routes(&block)
@replaced_routes = ActionController::Routing::Routes
new_routes = ActionController::Routing::RouteSet.new
silence_warnings do
ActionController::Routing.const_set('Routes', new_routes)
end
new_routes.draw(&block)
end
def build_response(&block)
klass = define_controller('Examples')
block ||= lambda { render :nothing => true }
klass.class_eval { define_method(:example, &block) }
define_routes do |map|
map.connect 'examples', :controller => 'examples', :action => 'example'
end
@controller = klass.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
get :example
@controller
end
def teardown_with_models
if @defined_constants
@defined_constants.each do |class_name|
Object.send(:remove_const, class_name)
end
end
if @created_tables
@created_tables.each do |table_name|
ActiveRecord::Base.
connection.
execute("DROP TABLE IF EXISTS #{table_name}")
end
end
if @replaced_routes
ActionController::Routing::Routes.clear!
silence_warnings do
ActionController::Routing.const_set('Routes', @replaced_routes)
end
@replaced_routes.reload!
end
teardown_without_models
end
alias_method :teardown_without_models, :teardown
alias_method :teardown, :teardown_with_models
end

View File

@@ -0,0 +1,18 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
class AutoloadMacroTest < Test::Unit::TestCase # :nodoc:
context "The macro auto-loader" do
should "load macros from the plugins" do
assert self.class.respond_to?('plugin_macro')
end
should "load macros from the gems" do
assert self.class.respond_to?('gem_macro')
end
should "load custom macros from ROOT/test/shoulda_macros" do
assert self.class.respond_to?('custom_macro')
end
end
end

View File

@@ -0,0 +1,145 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
class ContextTest < Test::Unit::TestCase # :nodoc:
def self.context_macro(&blk)
context "with a subcontext made by a macro" do
setup { @context_macro = :foo }
merge_block &blk
end
end
# def self.context_macro(&blk)
# context "with a subcontext made by a macro" do
# setup { @context_macro = :foo }
# yield # <- this doesn't work.
# end
# end
context "context with setup block" do
setup do
@blah = "blah"
end
should "run the setup block" do
assert_equal "blah", @blah
end
should "have name set right" do
assert_match(/^test: context with setup block/, self.to_s)
end
context "and a subcontext" do
setup do
@blah = "#{@blah} twice"
end
should "be named correctly" do
assert_match(/^test: context with setup block and a subcontext should be named correctly/, self.to_s)
end
should "run the setup blocks in order" do
assert_equal @blah, "blah twice"
end
end
context_macro do
should "have name set right" do
assert_match(/^test: context with setup block with a subcontext made by a macro should have name set right/, self.to_s)
end
should "run the setup block of that context macro" do
assert_equal :foo, @context_macro
end
should "run the setup block of the main context" do
assert_equal "blah", @blah
end
end
end
context "another context with setup block" do
setup do
@blah = "foo"
end
should "have @blah == 'foo'" do
assert_equal "foo", @blah
end
should "have name set right" do
assert_match(/^test: another context with setup block/, self.to_s)
end
end
context "context with method definition" do
setup do
def hello; "hi"; end
end
should "be able to read that method" do
assert_equal "hi", hello
end
should "have name set right" do
assert_match(/^test: context with method definition/, self.to_s)
end
end
context "another context" do
should "not define @blah" do
assert_nil @blah
end
end
context "context with multiple setups and/or teardowns" do
cleanup_count = 0
2.times do |i|
setup { cleanup_count += 1 }
teardown { cleanup_count -= 1 }
end
2.times do |i|
should "call all setups and all teardowns (check ##{i + 1})" do
assert_equal 2, cleanup_count
end
end
context "subcontexts" do
2.times do |i|
setup { cleanup_count += 1 }
teardown { cleanup_count -= 1 }
end
2.times do |i|
should "also call all setups and all teardowns in parent and subcontext (check ##{i + 1})" do
assert_equal 4, cleanup_count
end
end
end
end
should_eventually "pass, since it's unimplemented" do
flunk "what?"
end
should_eventually "not require a block when using should_eventually"
should "pass without a block, as that causes it to piggyback to should_eventually"
context "context for testing should piggybacking" do
should "call should_eventually as we are not passing a block"
end
context "context" do
context "with nested subcontexts" do
should_eventually "only print this statement once for a should_eventually"
end
end
end

View File

@@ -0,0 +1,63 @@
require 'test/unit'
class ConvertToShouldSyntaxTest < Test::Unit::TestCase # :nodoc:
BEFORE_FIXTURE = <<-EOS
class DummyTest < Test::Unit::TestCase
should "Not change this_word_with_underscores" do
end
def test_should_be_working
assert true
end
def test_some_cool_stuff
assert true
end
def non_test_method
end
end
EOS
AFTER_FIXTURE = <<-EOS
class DummyTest < Test::Unit::TestCase
should "Not change this_word_with_underscores" do
end
should "be working" do
assert true
end
should "RENAME ME: test some cool stuff" do
assert true
end
def non_test_method
end
end
EOS
FIXTURE_PATH = "./convert_to_should_syntax_fixture.dat"
RUBY = ENV['RUBY'] || 'ruby'
def test_convert_to_should_syntax
File.open(FIXTURE_PATH, "w") {|f| f.write(BEFORE_FIXTURE)}
cmd = "#{RUBY} #{File.join(File.dirname(__FILE__), '../../bin/convert_to_should_syntax')} #{FIXTURE_PATH}"
output = `#{cmd}`
File.unlink($1) if output.match(/has been stored in '([^']+)/)
assert_match(/has been converted/, output)
result = IO.read(FIXTURE_PATH)
assert_equal result, AFTER_FIXTURE
end
def teardown
File.unlink(FIXTURE_PATH)
end
end

View File

@@ -0,0 +1,241 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
require 'action_mailer'
require 'mocha'
class HelpersTest < Test::Unit::TestCase # :nodoc:
context "given delivered emails" do
setup do
email1 = stub(:subject => "one", :to => ["none1@email.com"])
email2 = stub(:subject => "two", :to => ["none2@email.com"])
ActionMailer::Base.stubs(:deliveries).returns([email1, email2])
end
should "have sent an email" do
assert_sent_email
assert_raises(Test::Unit::AssertionFailedError) do
assert_did_not_send_email
end
end
should "find email one" do
assert_sent_email do |e|
e.subject =~ /one/
end
end
should "not find an email that doesn't exist" do
assert_raises(Test::Unit::AssertionFailedError) do
assert_sent_email do |e|
e.subject =~ /whatever/
end
end
end
end
context "when there are no emails" do
setup do
ActionMailer::Base.stubs(:deliveries).returns([])
end
should "not have sent an email" do
assert_did_not_send_email
assert_raises(Test::Unit::AssertionFailedError) do
assert_sent_email
end
end
end
context "an array of values" do
setup do
@a = ['abc', 'def', 3]
end
[/b/, 'abc', 3].each do |x|
should "contain #{x.inspect}" do
assert_raises(Test::Unit::AssertionFailedError) do
assert_does_not_contain @a, x
end
assert_contains @a, x
end
end
should "not contain 'wtf'" do
assert_raises(Test::Unit::AssertionFailedError) {assert_contains @a, 'wtf'}
assert_does_not_contain @a, 'wtf'
end
should "be the same as another array, ordered differently" do
assert_same_elements(@a, [3, "def", "abc"])
assert_raises(Test::Unit::AssertionFailedError) do
assert_same_elements(@a, [3, 3, "def", "abc"])
end
assert_raises(Test::Unit::AssertionFailedError) do
assert_same_elements([@a, "abc"].flatten, [3, 3, "def", "abc"])
end
end
end
context "an array of values" do
setup do
@a = [1, 2, "(3)"]
end
context "after adding another value" do
setup do
@a.push(4)
end
should_change "@a.length", :by => 1
should_change "@a.length", :from => 3
should_change "@a.length", :to => 4
should_change "@a[0]", :by => 0
should_not_change "@a[0]"
end
context "after replacing it with an array of strings" do
setup do
@a = %w(a b c d e f)
end
should_change "@a.length", :by => 3
should_change "@a.length", :from => 3, :to => 6, :by => 3
should_change "@a[0]"
should_change "@a[1]", :from => 2, :to => "b"
should_change "@a[2]", :from => /\d/, :to => /\w/
should_change "@a[3]", :to => String
end
end
context "assert_good_value" do
should "validate a good email address" do
assert_good_value User.new, :email, "good@example.com"
end
should "validate a good SSN with a custom message" do
assert_good_value User.new, :ssn, "xxxxxxxxx", /length/
end
should "fail to validate a bad email address" do
assert_raises Test::Unit::AssertionFailedError do
assert_good_value User.new, :email, "bad"
end
end
should "fail to validate a bad SSN that is too short" do
assert_raises Test::Unit::AssertionFailedError do
assert_good_value User.new, :ssn, "x", /length/
end
end
should "accept a class as the first argument" do
assert_good_value User, :email, "good@example.com"
end
context "with an instance variable" do
setup do
@product = Product.new(:tangible => true)
end
should "use that instance variable" do
assert_good_value Product, :price, "9999", /included/
end
end
end
context "assert_bad_value" do
should "invalidate a bad email address" do
assert_bad_value User.new, :email, "bad"
end
should "invalidate a bad SSN with a custom message" do
assert_bad_value User.new, :ssn, "x", /length/
end
should "fail to invalidate a good email address" do
assert_raises Test::Unit::AssertionFailedError do
assert_bad_value User.new, :email, "good@example.com"
end
end
should "fail to invalidate a good SSN" do
assert_raises Test::Unit::AssertionFailedError do
assert_bad_value User.new, :ssn, "xxxxxxxxx", /length/
end
end
should "accept a class as the first argument" do
assert_bad_value User, :email, "bad"
end
context "with an instance variable" do
setup do
@product = Product.new(:tangible => true)
end
should "use that instance variable" do
assert_bad_value Product, :price, "0", /included/
end
end
end
context "a matching matcher" do
setup do
@matcher = stub('matcher', :matches? => true,
:failure_message => 'bad failure message',
:negative_failure_message => 'big time failure')
end
should "pass when given to assert_accepts" do
assert_accepts @matcher, 'target'
end
context "when given to assert_rejects" do
setup do
begin
assert_rejects @matcher, 'target'
rescue Test::Unit::AssertionFailedError => @error
end
end
should "fail" do
assert_not_nil @error
end
should "use the error message from the matcher" do
assert_equal 'big time failure', @error.message
end
end
end
context "a non-matching matcher" do
setup do
@matcher = stub('matcher', :matches? => false,
:failure_message => 'big time failure',
:negative_failure_message => 'bad failure message')
end
should "pass when given to assert_rejects" do
assert_rejects @matcher, 'target'
end
context "when given to assert_accepts" do
setup do
begin
assert_accepts @matcher, 'target'
rescue Test::Unit::AssertionFailedError => @error
end
end
should "fail" do
assert_not_nil @error
end
should "use the error message from the matcher" do
assert_equal 'big time failure', @error.message
end
end
end
end

View File

@@ -0,0 +1,34 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
class PrivateHelpersTest < Test::Unit::TestCase # :nodoc:
include Shoulda::Private
context "get_options!" do
should "remove opts from args" do
args = [:a, :b, {}]
get_options!(args)
assert_equal [:a, :b], args
end
should "return wanted opts in order" do
args = [{:one => 1, :two => 2}]
one, two = get_options!(args, :one, :two)
assert_equal 1, one
assert_equal 2, two
end
should "raise ArgumentError if given unwanted option" do
args = [{:one => 1, :two => 2}]
assert_raises ArgumentError do
get_options!(args, :one)
end
end
end
class ::SomeModel; end
context "model_class" do
should "sniff the class constant from the test class" do
self.expects(:name).returns("SomeModelTest")
assert_equal SomeModel, model_class
end
end
end

View File

@@ -0,0 +1,266 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
class ShouldTest < Test::Unit::TestCase # :nodoc:
should "be able to define a should statement outside of a context" do
assert true
end
should "see the name of my class as ShouldTest" do
assert_equal "ShouldTest", self.class.name
end
def self.should_see_class_methods
should "be able to see class methods" do
assert true
end
end
def self.should_see_a_context_block_like_a_Test_Unit_class
should "see a context block as a Test::Unit class" do
assert_equal "ShouldTest", self.class.name
end
end
def self.should_see_blah
should "see @blah through a macro" do
assert @blah
end
end
def self.should_not_see_blah
should "not see @blah through a macro" do
assert_nil @blah
end
end
def self.should_be_able_to_make_context_macros(prefix = nil)
context "a macro" do
should "have the tests named correctly" do
assert_match(/^test: #{prefix}a macro should have the tests named correctly/, self.to_s)
end
end
end
context "Context" do
should_see_class_methods
should_see_a_context_block_like_a_Test_Unit_class
should_be_able_to_make_context_macros("Context ")
should "not define @blah" do
assert ! self.instance_variables.include?("@blah")
end
should_not_see_blah
should "be able to define a should statement" do
assert true
end
should "see the name of my class as ShouldTest" do
assert_equal "ShouldTest", self.class.name
end
context "with a subcontext" do
should_be_able_to_make_context_macros("Context with a subcontext ")
end
end
context "Context with setup block" do
setup do
@blah = "blah"
end
should "have @blah == 'blah'" do
assert_equal "blah", @blah
end
should_see_blah
should "have name set right" do
assert_match(/^test: Context with setup block/, self.to_s)
end
context "and a subcontext" do
setup do
@blah = "#{@blah} twice"
end
should "be named correctly" do
assert_match(/^test: Context with setup block and a subcontext should be named correctly/, self.to_s)
end
should "run the setup methods in order" do
assert_equal @blah, "blah twice"
end
should_see_blah
end
end
context "Another context with setup block" do
setup do
@blah = "foo"
end
should "have @blah == 'foo'" do
assert_equal "foo", @blah
end
should "have name set right" do
assert_match(/^test: Another context with setup block/, self.to_s)
end
should_see_blah
end
should_eventually "pass, since it's a should_eventually" do
flunk "what?"
end
# Context creation and naming
def test_should_create_a_new_context
assert_nothing_raised do
Shoulda::Context.new("context name", self) do; end
end
end
def test_should_create_a_nested_context
assert_nothing_raised do
parent = Shoulda::Context.new("Parent", self) do; end
child = Shoulda::Context.new("Child", parent) do; end
end
end
def test_should_name_a_contexts_correctly
parent = Shoulda::Context.new("Parent", self) do; end
child = Shoulda::Context.new("Child", parent) do; end
grandchild = Shoulda::Context.new("GrandChild", child) do; end
assert_equal "Parent", parent.full_name
assert_equal "Parent Child", child.full_name
assert_equal "Parent Child GrandChild", grandchild.full_name
end
# Should statements
def test_should_have_should_hashes_when_given_should_statements
context = Shoulda::Context.new("name", self) do
should "be good" do; end
should "another" do; end
end
names = context.shoulds.map {|s| s[:name]}
assert_equal ["another", "be good"], names.sort
end
# setup and teardown
def test_should_capture_setup_and_teardown_blocks
context = Shoulda::Context.new("name", self) do
setup do; "setup"; end
teardown do; "teardown"; end
end
assert_equal "setup", context.setup_blocks.first.call
assert_equal "teardown", context.teardown_blocks.first.call
end
# building
def test_should_create_shoulda_test_for_each_should_on_build
context = Shoulda::Context.new("name", self) do
should "one" do; end
should "two" do; end
end
context.expects(:create_test_from_should_hash).with(has_entry(:name => "one"))
context.expects(:create_test_from_should_hash).with(has_entry(:name => "two"))
context.build
end
def test_should_create_test_methods_on_build
tu_class = Test::Unit::TestCase
context = Shoulda::Context.new("A Context", tu_class) do
should "define the test" do; end
end
tu_class.expects(:define_method).with(:"test: A Context should define the test. ")
context.build
end
def test_should_create_test_methods_on_build_when_subcontext
tu_class = Test::Unit::TestCase
context = Shoulda::Context.new("A Context", tu_class) do
context "with a child" do
should "define the test" do; end
end
end
tu_class.expects(:define_method).with(:"test: A Context with a child should define the test. ")
context.build
end
# Test::Unit integration
def test_should_create_a_new_context_and_build_it_on_Test_Unit_context
c = mock("context")
c.expects(:build)
Shoulda::Context.expects(:new).with("foo", kind_of(Class)).returns(c)
self.class.context "foo" do; end
end
def test_should_create_a_one_off_context_and_build_it_on_Test_Unit_should
s = mock("test")
Shoulda::Context.any_instance.expects(:should).with("rock", {}).returns(s)
Shoulda::Context.any_instance.expects(:build)
self.class.should "rock" do; end
end
def test_should_create_a_one_off_context_and_build_it_on_Test_Unit_should_eventually
s = mock("test")
Shoulda::Context.any_instance.expects(:should_eventually).with("rock").returns(s)
Shoulda::Context.any_instance.expects(:build)
self.class.should_eventually "rock" do; end
end
should "run a :before proc", :before => lambda { @value = "before" } do
assert_equal "before", @value
end
context "A :before proc" do
setup do
assert_equal "before", @value
@value = "setup"
end
should "run before the current setup", :before => lambda { @value = "before" } do
assert_equal "setup", @value
end
end
context "a before statement" do
setup do
assert_equal "before", @value
@value = "setup"
end
before_should "run before the current setup" do
@value = "before"
end
end
context "A context" do
setup do
@value = "outer"
end
context "with a subcontext and a :before proc" do
before = lambda do
assert "outer", @value
@value = "before"
end
should "run after the parent setup", :before => before do
assert_equal "before", @value
end
end
end
end

View File

@@ -0,0 +1,25 @@
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_rails_root_session_id'
def ensure_logged_in
unless session[:logged_in]
respond_to do |accepts|
accepts.html do
flash[:error] = 'What do you think you\'re doing?'
redirect_to '/'
end
accepts.xml do
headers["Status"] = "Unauthorized"
headers["WWW-Authenticate"] = %(Basic realm="Web Password")
render :text => "Couldn't authenticate you", :status => '401 Unauthorized'
end
end
return false
end
return true
end
end

View File

@@ -0,0 +1,86 @@
class PostsController < ApplicationController
before_filter :ensure_logged_in
before_filter :load_user
def index
@posts = @user.posts
respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @posts.to_xml }
format.rss do
headers['Content-Type'] = 'application/rss+xml'
session[:special] = '$2 off your next purchase'
session[:special_user_id] = @user.id
head :ok
end
end
end
def show
@post = @user.posts.find(params[:id])
@false_flag = false
respond_to do |format|
format.html { render :layout => 'wide' }
format.xml { render :xml => @post.to_xml }
end
end
def new
@post = @user.posts.build
render :layout => false
end
def edit
@post = @user.posts.find(params[:id])
end
def create
@post = @user.posts.build(params[:post])
respond_to do |format|
if @post.save
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to user_post_url(@post.user, @post) }
format.xml { head :created, :location => user_post_url(@post.user, @post) }
else
format.html { render :action => "new" }
format.xml { render :xml => @post.errors.to_xml }
end
end
end
def update
@post = @user.posts.find(params[:id])
respond_to do |format|
if @post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
format.html { redirect_to user_post_url(@post.user, @post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @post.errors.to_xml }
end
end
end
def destroy
@post = @user.posts.find(params[:id])
@post.destroy
flash[:notice] = "Post was removed"
respond_to do |format|
format.html { redirect_to user_posts_url(@post.user) }
format.xml { head :ok }
end
end
private
def load_user
@user = User.find(params[:user_id])
end
end

Some files were not shown because too many files have changed in this diff Show More