tpitale-shoulda 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +171 -0
- data/Rakefile +71 -0
- data/bin/convert_to_should_syntax +42 -0
- data/lib/shoulda.rb +11 -0
- data/lib/shoulda/action_controller.rb +26 -0
- data/lib/shoulda/action_controller/macros.rb +240 -0
- data/lib/shoulda/action_controller/matchers.rb +37 -0
- data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
- data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
- data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
- data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +74 -0
- data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
- data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
- data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
- data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
- data/lib/shoulda/action_mailer.rb +10 -0
- data/lib/shoulda/action_mailer/assertions.rb +38 -0
- data/lib/shoulda/action_view.rb +10 -0
- data/lib/shoulda/action_view/macros.rb +61 -0
- data/lib/shoulda/active_record.rb +16 -0
- data/lib/shoulda/active_record/assertions.rb +69 -0
- data/lib/shoulda/active_record/helpers.rb +27 -0
- data/lib/shoulda/active_record/macros.rb +512 -0
- data/lib/shoulda/active_record/matchers.rb +43 -0
- data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
- data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
- data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
- data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
- data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
- data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
- data/lib/shoulda/active_record/matchers/have_db_index_matcher.rb +112 -0
- data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +128 -0
- data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
- data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
- data/lib/shoulda/active_record/matchers/validate_format_of_matcher.rb +67 -0
- data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
- data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
- data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
- data/lib/shoulda/active_record/matchers/validation_matcher.rb +57 -0
- data/lib/shoulda/assertions.rb +71 -0
- data/lib/shoulda/autoload_macros.rb +46 -0
- data/lib/shoulda/context.rb +413 -0
- data/lib/shoulda/helpers.rb +8 -0
- data/lib/shoulda/macros.rb +133 -0
- data/lib/shoulda/minitest.rb +24 -0
- data/lib/shoulda/private_helpers.rb +13 -0
- data/lib/shoulda/proc_extensions.rb +14 -0
- data/lib/shoulda/rails.rb +13 -0
- data/lib/shoulda/rspec.rb +11 -0
- data/lib/shoulda/tasks.rb +3 -0
- data/lib/shoulda/tasks/list_tests.rake +29 -0
- data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
- data/lib/shoulda/test_unit.rb +22 -0
- data/rails/init.rb +7 -0
- data/test/README +36 -0
- data/test/fail_macros.rb +39 -0
- data/test/fixtures/addresses.yml +3 -0
- data/test/fixtures/friendships.yml +0 -0
- data/test/fixtures/posts.yml +5 -0
- data/test/fixtures/products.yml +0 -0
- data/test/fixtures/taggings.yml +0 -0
- data/test/fixtures/tags.yml +9 -0
- data/test/fixtures/users.yml +6 -0
- data/test/functional/posts_controller_test.rb +121 -0
- data/test/functional/users_controller_test.rb +19 -0
- data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
- data/test/matchers/active_record/allow_value_matcher_test.rb +64 -0
- data/test/matchers/active_record/association_matcher_test.rb +263 -0
- data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
- data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
- data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
- data/test/matchers/active_record/have_db_index_matcher_test.rb +91 -0
- data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
- data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
- data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
- data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
- data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
- data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
- data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
- data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
- data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
- data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
- data/test/matchers/controller/respond_with_content_type_matcher_test.rb +32 -0
- data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
- data/test/matchers/controller/route_matcher_test.rb +58 -0
- data/test/matchers/controller/set_session_matcher_test.rb +38 -0
- data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
- data/test/model_builder.rb +106 -0
- data/test/other/autoload_macro_test.rb +18 -0
- data/test/other/context_test.rb +203 -0
- data/test/other/convert_to_should_syntax_test.rb +63 -0
- data/test/other/helpers_test.rb +340 -0
- data/test/other/private_helpers_test.rb +32 -0
- data/test/other/should_test.rb +271 -0
- data/test/rails_root/app/controllers/application_controller.rb +25 -0
- data/test/rails_root/app/controllers/posts_controller.rb +87 -0
- data/test/rails_root/app/controllers/users_controller.rb +84 -0
- data/test/rails_root/app/helpers/application_helper.rb +3 -0
- data/test/rails_root/app/helpers/posts_helper.rb +2 -0
- data/test/rails_root/app/helpers/users_helper.rb +2 -0
- data/test/rails_root/app/models/address.rb +7 -0
- data/test/rails_root/app/models/flea.rb +3 -0
- data/test/rails_root/app/models/friendship.rb +4 -0
- data/test/rails_root/app/models/pets/cat.rb +7 -0
- data/test/rails_root/app/models/pets/dog.rb +10 -0
- data/test/rails_root/app/models/post.rb +12 -0
- data/test/rails_root/app/models/product.rb +12 -0
- data/test/rails_root/app/models/profile.rb +2 -0
- data/test/rails_root/app/models/registration.rb +2 -0
- data/test/rails_root/app/models/tag.rb +8 -0
- data/test/rails_root/app/models/tagging.rb +4 -0
- data/test/rails_root/app/models/treat.rb +3 -0
- data/test/rails_root/app/models/user.rb +32 -0
- data/test/rails_root/app/views/layouts/posts.rhtml +19 -0
- data/test/rails_root/app/views/layouts/users.rhtml +17 -0
- data/test/rails_root/app/views/layouts/wide.html.erb +1 -0
- data/test/rails_root/app/views/posts/edit.rhtml +27 -0
- data/test/rails_root/app/views/posts/index.rhtml +25 -0
- data/test/rails_root/app/views/posts/new.rhtml +26 -0
- data/test/rails_root/app/views/posts/show.rhtml +18 -0
- data/test/rails_root/app/views/users/edit.rhtml +22 -0
- data/test/rails_root/app/views/users/index.rhtml +22 -0
- data/test/rails_root/app/views/users/new.rhtml +21 -0
- data/test/rails_root/app/views/users/show.rhtml +13 -0
- data/test/rails_root/config/boot.rb +110 -0
- data/test/rails_root/config/database.yml +4 -0
- data/test/rails_root/config/environment.rb +18 -0
- data/test/rails_root/config/environments/test.rb +0 -0
- data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
- data/test/rails_root/config/initializers/shoulda.rb +8 -0
- data/test/rails_root/config/routes.rb +6 -0
- data/test/rails_root/db/migrate/001_create_users.rb +19 -0
- data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
- data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
- data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
- data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
- data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
- data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
- data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
- data/test/rails_root/db/migrate/009_create_products.rb +17 -0
- data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
- data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
- data/test/rails_root/db/migrate/20090506203502_create_profiles.rb +12 -0
- data/test/rails_root/db/migrate/20090506203536_create_registrations.rb +14 -0
- data/test/rails_root/db/migrate/20090513104502_create_cats.rb +12 -0
- data/test/rails_root/db/schema.rb +0 -0
- data/test/rails_root/log/test.log +1 -0
- data/test/rails_root/public/404.html +30 -0
- data/test/rails_root/public/422.html +30 -0
- data/test/rails_root/public/500.html +30 -0
- data/test/rails_root/script/console +3 -0
- data/test/rails_root/script/generate +3 -0
- data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
- data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
- data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
- data/test/rspec_test.rb +207 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/address_test.rb +10 -0
- data/test/unit/cat_test.rb +7 -0
- data/test/unit/dog_test.rb +9 -0
- data/test/unit/flea_test.rb +6 -0
- data/test/unit/friendship_test.rb +6 -0
- data/test/unit/post_test.rb +19 -0
- data/test/unit/product_test.rb +23 -0
- data/test/unit/tag_test.rb +15 -0
- data/test/unit/tagging_test.rb +6 -0
- data/test/unit/user_test.rb +80 -0
- metadata +226 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
class ValidationMatcher # :nodoc:
|
6
|
+
|
7
|
+
attr_reader :failure_message
|
8
|
+
|
9
|
+
def initialize(attribute)
|
10
|
+
@attribute = attribute
|
11
|
+
end
|
12
|
+
|
13
|
+
def negative_failure_message
|
14
|
+
@negative_failure_message || @failure_message
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(subject)
|
18
|
+
@subject = subject
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def allows_value_of(value, message = nil)
|
26
|
+
allow = AllowValueMatcher.
|
27
|
+
new(value).
|
28
|
+
for(@attribute).
|
29
|
+
with_message(message)
|
30
|
+
if allow.matches?(@subject)
|
31
|
+
@negative_failure_message = allow.failure_message
|
32
|
+
true
|
33
|
+
else
|
34
|
+
@failure_message = allow.negative_failure_message
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def disallows_value_of(value, message = nil)
|
40
|
+
disallow = AllowValueMatcher.
|
41
|
+
new(value).
|
42
|
+
for(@attribute).
|
43
|
+
with_message(message)
|
44
|
+
if disallow.matches?(@subject)
|
45
|
+
@failure_message = disallow.negative_failure_message
|
46
|
+
false
|
47
|
+
else
|
48
|
+
@negative_failure_message = disallow.failure_message
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Assertions
|
3
|
+
# Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
|
4
|
+
#
|
5
|
+
# assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
|
6
|
+
def assert_same_elements(a1, a2, msg = nil)
|
7
|
+
[:select, :inject, :size].each do |m|
|
8
|
+
[a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
|
9
|
+
end
|
10
|
+
|
11
|
+
assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
|
12
|
+
assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
|
13
|
+
|
14
|
+
assert_equal(a1h, a2h, msg)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Asserts that the given collection contains item x. If x is a regular expression, ensure that
|
18
|
+
# at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
|
19
|
+
#
|
20
|
+
# assert_contains(['a', '1'], /\d/) => passes
|
21
|
+
# assert_contains(['a', '1'], 'a') => passes
|
22
|
+
# assert_contains(['a', '1'], /not there/) => fails
|
23
|
+
def assert_contains(collection, x, extra_msg = "")
|
24
|
+
collection = [collection] unless collection.is_a?(Array)
|
25
|
+
msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
|
26
|
+
case x
|
27
|
+
when Regexp
|
28
|
+
assert(collection.detect { |e| e =~ x }, msg)
|
29
|
+
else
|
30
|
+
assert(collection.include?(x), msg)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
|
35
|
+
# none of the elements from the collection match x.
|
36
|
+
def assert_does_not_contain(collection, x, extra_msg = "")
|
37
|
+
collection = [collection] unless collection.is_a?(Array)
|
38
|
+
msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg
|
39
|
+
case x
|
40
|
+
when Regexp
|
41
|
+
assert(!collection.detect { |e| e =~ x }, msg)
|
42
|
+
else
|
43
|
+
assert(!collection.include?(x), msg)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Asserts that the given matcher returns true when +target+ is passed to #matches?
|
48
|
+
def assert_accepts(matcher, target, options = {})
|
49
|
+
if matcher.matches?(target)
|
50
|
+
assert_block { true }
|
51
|
+
if options[:message]
|
52
|
+
assert_match options[:message], matcher.negative_failure_message
|
53
|
+
end
|
54
|
+
else
|
55
|
+
assert_block(matcher.failure_message) { false }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Asserts that the given matcher returns false when +target+ is passed to #matches?
|
60
|
+
def assert_rejects(matcher, target, options = {})
|
61
|
+
unless matcher.matches?(target)
|
62
|
+
assert_block { true }
|
63
|
+
if options[:message]
|
64
|
+
assert_match options[:message], matcher.failure_message
|
65
|
+
end
|
66
|
+
else
|
67
|
+
assert_block(matcher.negative_failure_message) { false }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
# Call autoload_macros when you want to load test macros automatically in a non-Rails
|
3
|
+
# project (it's done automatically for Rails projects).
|
4
|
+
# You don't need to specify ROOT/test/shoulda_macros explicitly. Your custom macros
|
5
|
+
# are loaded automatically when you call autoload_macros.
|
6
|
+
#
|
7
|
+
# The first argument is the path to you application's root directory.
|
8
|
+
# All following arguments are directories relative to your root, which contain
|
9
|
+
# shoulda_macros subdirectories. These directories support the same kinds of globs as the
|
10
|
+
# Dir class.
|
11
|
+
#
|
12
|
+
# Basic usage (from a test_helper):
|
13
|
+
# Shoulda.autoload_macros(File.dirname(__FILE__) + '/..')
|
14
|
+
# will load everything in
|
15
|
+
# - your_app/test/shoulda_macros
|
16
|
+
#
|
17
|
+
# To load vendored macros as well:
|
18
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/*')
|
19
|
+
# will load everything in
|
20
|
+
# - APP_ROOT/vendor/*/shoulda_macros
|
21
|
+
# - APP_ROOT/test/shoulda_macros
|
22
|
+
#
|
23
|
+
# To load macros in an app with a vendor directory laid out like Rails':
|
24
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/{plugins,gems}/*')
|
25
|
+
# or
|
26
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/plugins/*', 'vendor/gems/*')
|
27
|
+
# will load everything in
|
28
|
+
# - APP_ROOT/vendor/plugins/*/shoulda_macros
|
29
|
+
# - APP_ROOT/vendor/gems/*/shoulda_macros
|
30
|
+
# - APP_ROOT/test/shoulda_macros
|
31
|
+
#
|
32
|
+
# If you prefer to stick testing dependencies away from your production dependencies:
|
33
|
+
# Shoulda.autoload_macros(APP_ROOT, 'vendor/*', 'test/vendor/*')
|
34
|
+
# will load everything in
|
35
|
+
# - APP_ROOT/vendor/*/shoulda_macros
|
36
|
+
# - APP_ROOT/test/vendor/*/shoulda_macros
|
37
|
+
# - APP_ROOT/test/shoulda_macros
|
38
|
+
def self.autoload_macros(root, *dirs)
|
39
|
+
dirs << File.join('test')
|
40
|
+
complete_dirs = dirs.map{|d| File.join(root, d, 'shoulda_macros')}
|
41
|
+
all_files = complete_dirs.inject([]){ |files, dir| files + Dir[File.join(dir, '*.rb')] }
|
42
|
+
all_files.each do |file|
|
43
|
+
require file
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,413 @@
|
|
1
|
+
module Shoulda
|
2
|
+
class << self
|
3
|
+
attr_accessor :contexts
|
4
|
+
def contexts # :nodoc:
|
5
|
+
@contexts ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def current_context # :nodoc:
|
9
|
+
self.contexts.last
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_context(context) # :nodoc:
|
13
|
+
self.contexts.push(context)
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove_context # :nodoc:
|
17
|
+
self.contexts.pop
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# == Should statements
|
23
|
+
#
|
24
|
+
# Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
|
25
|
+
# contains all the normal code and assertions you're used to seeing, with the added benefit that
|
26
|
+
# they can be wrapped inside context blocks (see below).
|
27
|
+
#
|
28
|
+
# === Example:
|
29
|
+
#
|
30
|
+
# class UserTest < Test::Unit::TestCase
|
31
|
+
#
|
32
|
+
# def setup
|
33
|
+
# @user = User.new("John", "Doe")
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# should "return its full name"
|
37
|
+
# assert_equal 'John Doe', @user.full_name
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# ...will produce the following test:
|
43
|
+
# * <tt>"test: User should return its full name. "</tt>
|
44
|
+
#
|
45
|
+
# Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
|
46
|
+
#
|
47
|
+
# Should statements can also take a Proc as a <tt>:before </tt>option. This proc runs after any
|
48
|
+
# parent context's setups but before the current context's setup.
|
49
|
+
#
|
50
|
+
# === Example:
|
51
|
+
#
|
52
|
+
# context "Some context" do
|
53
|
+
# setup { puts("I run after the :before proc") }
|
54
|
+
#
|
55
|
+
# should "run a :before proc", :before => lambda { puts("I run before the setup") } do
|
56
|
+
# assert true
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
|
60
|
+
def should(name, options = {}, &blk)
|
61
|
+
if Shoulda.current_context
|
62
|
+
block_given? ? Shoulda.current_context.should(name, options, &blk) : Shoulda.current_context.should_eventually(name)
|
63
|
+
else
|
64
|
+
context_name = self.name.gsub(/Test/, "")
|
65
|
+
context = Shoulda::Context.new(context_name, self) do
|
66
|
+
block_given? ? should(name, options, &blk) : should_eventually(name)
|
67
|
+
end
|
68
|
+
context.build
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# == Before statements
|
73
|
+
#
|
74
|
+
# Before statements are should statements that run before the current
|
75
|
+
# context's setup. These are especially useful when setting expectations.
|
76
|
+
#
|
77
|
+
# === Example:
|
78
|
+
#
|
79
|
+
# class UserControllerTest < Test::Unit::TestCase
|
80
|
+
# context "the index action" do
|
81
|
+
# setup do
|
82
|
+
# @users = [Factory(:user)]
|
83
|
+
# User.stubs(:find).returns(@users)
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# context "on GET" do
|
87
|
+
# setup { get :index }
|
88
|
+
#
|
89
|
+
# should_respond_with :success
|
90
|
+
#
|
91
|
+
# # runs before "get :index"
|
92
|
+
# before_should "find all users" do
|
93
|
+
# User.expects(:find).with(:all).returns(@users)
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
def before_should(name, &blk)
|
99
|
+
should(name, :before => blk) { assert true }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
|
103
|
+
def should_eventually(name, options = {}, &blk)
|
104
|
+
context_name = self.name.gsub(/Test/, "")
|
105
|
+
context = Shoulda::Context.new(context_name, self) do
|
106
|
+
should_eventually(name, &blk)
|
107
|
+
end
|
108
|
+
context.build
|
109
|
+
end
|
110
|
+
|
111
|
+
# == Contexts
|
112
|
+
#
|
113
|
+
# A context block groups should statements under a common set of setup/teardown methods.
|
114
|
+
# Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
|
115
|
+
# and readability of your test code.
|
116
|
+
#
|
117
|
+
# A context block can contain setup, should, should_eventually, and teardown blocks.
|
118
|
+
#
|
119
|
+
# class UserTest < Test::Unit::TestCase
|
120
|
+
# context "A User instance" do
|
121
|
+
# setup do
|
122
|
+
# @user = User.find(:first)
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# should "return its full name"
|
126
|
+
# assert_equal 'John Doe', @user.full_name
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# This code will produce the method <tt>"test: A User instance should return its full name. "</tt>.
|
132
|
+
#
|
133
|
+
# Contexts may be nested. Nested contexts run their setup blocks from out to in before each
|
134
|
+
# should statement. They then run their teardown blocks from in to out after each should statement.
|
135
|
+
#
|
136
|
+
# class UserTest < Test::Unit::TestCase
|
137
|
+
# context "A User instance" do
|
138
|
+
# setup do
|
139
|
+
# @user = User.find(:first)
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# should "return its full name"
|
143
|
+
# assert_equal 'John Doe', @user.full_name
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# context "with a profile" do
|
147
|
+
# setup do
|
148
|
+
# @user.profile = Profile.find(:first)
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# should "return true when sent :has_profile?"
|
152
|
+
# assert @user.has_profile?
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# This code will produce the following methods
|
159
|
+
# * <tt>"test: A User instance should return its full name. "</tt>
|
160
|
+
# * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
|
161
|
+
#
|
162
|
+
# <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
|
163
|
+
# tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
|
164
|
+
|
165
|
+
def context(name, &blk)
|
166
|
+
if Shoulda.current_context
|
167
|
+
Shoulda.current_context.context(name, &blk)
|
168
|
+
else
|
169
|
+
context = Shoulda::Context.new(name, self, &blk)
|
170
|
+
context.build
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns the class being tested, as determined by the test class name.
|
175
|
+
#
|
176
|
+
# class UserTest; described_type; end
|
177
|
+
# # => User
|
178
|
+
def described_type
|
179
|
+
self.name.gsub(/Test$/, '').constantize
|
180
|
+
end
|
181
|
+
|
182
|
+
# Sets the return value of the subject instance method:
|
183
|
+
#
|
184
|
+
# class UserTest < Test::Unit::TestCase
|
185
|
+
# subject { User.first }
|
186
|
+
#
|
187
|
+
# # uses the existing user
|
188
|
+
# should_validate_uniqueness_of :email
|
189
|
+
# end
|
190
|
+
def subject(&block)
|
191
|
+
@subject_block = block
|
192
|
+
end
|
193
|
+
|
194
|
+
def subject_block # :nodoc:
|
195
|
+
@subject_block
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
module InstanceMethods
|
200
|
+
# Returns an instance of the class under test.
|
201
|
+
#
|
202
|
+
# class UserTest
|
203
|
+
# should "be a user" do
|
204
|
+
# assert_kind_of User, subject # passes
|
205
|
+
# end
|
206
|
+
# end
|
207
|
+
#
|
208
|
+
# The subject can be explicitly set using the subject class method:
|
209
|
+
#
|
210
|
+
# class UserTest
|
211
|
+
# subject { User.first }
|
212
|
+
# should "be an existing user" do
|
213
|
+
# assert !subject.new_record? # uses the first user
|
214
|
+
# end
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# If an instance variable exists named after the described class, that
|
218
|
+
# instance variable will be used as the subject. This behavior is
|
219
|
+
# deprecated, and will be removed in a future version of Shoulda. The
|
220
|
+
# recommended approach for using a different subject is to use the subject
|
221
|
+
# class method.
|
222
|
+
#
|
223
|
+
# class UserTest
|
224
|
+
# should "be the existing user" do
|
225
|
+
# @user = User.new
|
226
|
+
# assert_equal @user, subject # passes
|
227
|
+
# end
|
228
|
+
# end
|
229
|
+
#
|
230
|
+
# The subject is used by all macros that require an instance of the class
|
231
|
+
# being tested.
|
232
|
+
def subject
|
233
|
+
@shoulda_subject ||= construct_subject
|
234
|
+
end
|
235
|
+
|
236
|
+
def subject_block # :nodoc:
|
237
|
+
(@shoulda_context && @shoulda_context.subject_block) || self.class.subject_block
|
238
|
+
end
|
239
|
+
|
240
|
+
def get_instance_of(object_or_klass) # :nodoc:
|
241
|
+
if object_or_klass.is_a?(Class)
|
242
|
+
klass = object_or_klass
|
243
|
+
ivar = "@#{instance_variable_name_for(klass)}"
|
244
|
+
if instance = instance_variable_get(ivar)
|
245
|
+
warn "[WARNING] Using #{ivar} as the subject. Future versions " <<
|
246
|
+
"of Shoulda will require an explicit subject using the " <<
|
247
|
+
"subject class method. Add this after your setup to avoid " <<
|
248
|
+
"this warning: subject { #{ivar} }"
|
249
|
+
instance
|
250
|
+
else
|
251
|
+
klass.new
|
252
|
+
end
|
253
|
+
else
|
254
|
+
object_or_klass
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def instance_variable_name_for(klass) # :nodoc:
|
259
|
+
klass.to_s.split('::').last.underscore
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
def construct_subject
|
265
|
+
if subject_block
|
266
|
+
instance_eval(&subject_block)
|
267
|
+
else
|
268
|
+
get_instance_of(self.class.described_type)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class Context # :nodoc:
|
274
|
+
|
275
|
+
attr_accessor :name # my name
|
276
|
+
attr_accessor :parent # may be another context, or the original test::unit class.
|
277
|
+
attr_accessor :subcontexts # array of contexts nested under myself
|
278
|
+
attr_accessor :setup_blocks # blocks given via setup methods
|
279
|
+
attr_accessor :teardown_blocks # blocks given via teardown methods
|
280
|
+
attr_accessor :shoulds # array of hashes representing the should statements
|
281
|
+
attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
|
282
|
+
attr_accessor :subject_block
|
283
|
+
|
284
|
+
def initialize(name, parent, &blk)
|
285
|
+
Shoulda.add_context(self)
|
286
|
+
self.name = name
|
287
|
+
self.parent = parent
|
288
|
+
self.setup_blocks = []
|
289
|
+
self.teardown_blocks = []
|
290
|
+
self.shoulds = []
|
291
|
+
self.should_eventuallys = []
|
292
|
+
self.subcontexts = []
|
293
|
+
|
294
|
+
merge_block(&blk)
|
295
|
+
Shoulda.remove_context
|
296
|
+
end
|
297
|
+
|
298
|
+
def merge_block(&blk)
|
299
|
+
blk.bind(self).call
|
300
|
+
end
|
301
|
+
|
302
|
+
def context(name, &blk)
|
303
|
+
self.subcontexts << Context.new(name, self, &blk)
|
304
|
+
end
|
305
|
+
|
306
|
+
def setup(&blk)
|
307
|
+
self.setup_blocks << blk
|
308
|
+
end
|
309
|
+
|
310
|
+
def teardown(&blk)
|
311
|
+
self.teardown_blocks << blk
|
312
|
+
end
|
313
|
+
|
314
|
+
def should(name, options = {}, &blk)
|
315
|
+
if block_given?
|
316
|
+
self.shoulds << { :name => name, :before => options[:before], :block => blk }
|
317
|
+
else
|
318
|
+
self.should_eventuallys << { :name => name }
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def should_eventually(name, &blk)
|
323
|
+
self.should_eventuallys << { :name => name, :block => blk }
|
324
|
+
end
|
325
|
+
|
326
|
+
def subject(&block)
|
327
|
+
self.subject_block = block
|
328
|
+
end
|
329
|
+
|
330
|
+
def subject_block
|
331
|
+
return @subject_block if @subject_block
|
332
|
+
parent.subject_block
|
333
|
+
end
|
334
|
+
|
335
|
+
def full_name
|
336
|
+
parent_name = parent.full_name if am_subcontext?
|
337
|
+
return [parent_name, name].join(" ").strip
|
338
|
+
end
|
339
|
+
|
340
|
+
def am_subcontext?
|
341
|
+
parent.is_a?(self.class) # my parent is the same class as myself.
|
342
|
+
end
|
343
|
+
|
344
|
+
def test_unit_class
|
345
|
+
am_subcontext? ? parent.test_unit_class : parent
|
346
|
+
end
|
347
|
+
|
348
|
+
def create_test_from_should_hash(should)
|
349
|
+
test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
|
350
|
+
|
351
|
+
if test_unit_class.instance_methods.include?(test_name.to_s)
|
352
|
+
warn " * WARNING: '#{test_name}' is already defined"
|
353
|
+
end
|
354
|
+
|
355
|
+
context = self
|
356
|
+
test_unit_class.send(:define_method, test_name) do
|
357
|
+
@shoulda_context = context
|
358
|
+
begin
|
359
|
+
context.run_parent_setup_blocks(self)
|
360
|
+
should[:before].bind(self).call if should[:before]
|
361
|
+
context.run_current_setup_blocks(self)
|
362
|
+
should[:block].bind(self).call
|
363
|
+
ensure
|
364
|
+
context.run_all_teardown_blocks(self)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def run_all_setup_blocks(binding)
|
370
|
+
run_parent_setup_blocks(binding)
|
371
|
+
run_current_setup_blocks(binding)
|
372
|
+
end
|
373
|
+
|
374
|
+
def run_parent_setup_blocks(binding)
|
375
|
+
self.parent.run_all_setup_blocks(binding) if am_subcontext?
|
376
|
+
end
|
377
|
+
|
378
|
+
def run_current_setup_blocks(binding)
|
379
|
+
setup_blocks.each do |setup_block|
|
380
|
+
setup_block.bind(binding).call
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def run_all_teardown_blocks(binding)
|
385
|
+
teardown_blocks.reverse.each do |teardown_block|
|
386
|
+
teardown_block.bind(binding).call
|
387
|
+
end
|
388
|
+
self.parent.run_all_teardown_blocks(binding) if am_subcontext?
|
389
|
+
end
|
390
|
+
|
391
|
+
def print_should_eventuallys
|
392
|
+
should_eventuallys.each do |should|
|
393
|
+
test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
|
394
|
+
puts " * DEFERRED: " + test_name
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
def build
|
399
|
+
shoulds.each do |should|
|
400
|
+
create_test_from_should_hash(should)
|
401
|
+
end
|
402
|
+
|
403
|
+
subcontexts.each { |context| context.build }
|
404
|
+
|
405
|
+
print_should_eventuallys
|
406
|
+
end
|
407
|
+
|
408
|
+
def method_missing(method, *args, &blk)
|
409
|
+
test_unit_class.send(method, *args, &blk)
|
410
|
+
end
|
411
|
+
|
412
|
+
end
|
413
|
+
end
|