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.
Files changed (170) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +171 -0
  4. data/Rakefile +71 -0
  5. data/bin/convert_to_should_syntax +42 -0
  6. data/lib/shoulda.rb +11 -0
  7. data/lib/shoulda/action_controller.rb +26 -0
  8. data/lib/shoulda/action_controller/macros.rb +240 -0
  9. data/lib/shoulda/action_controller/matchers.rb +37 -0
  10. data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
  11. data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
  12. data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
  13. data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +74 -0
  14. data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
  15. data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
  16. data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
  17. data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
  18. data/lib/shoulda/action_mailer.rb +10 -0
  19. data/lib/shoulda/action_mailer/assertions.rb +38 -0
  20. data/lib/shoulda/action_view.rb +10 -0
  21. data/lib/shoulda/action_view/macros.rb +61 -0
  22. data/lib/shoulda/active_record.rb +16 -0
  23. data/lib/shoulda/active_record/assertions.rb +69 -0
  24. data/lib/shoulda/active_record/helpers.rb +27 -0
  25. data/lib/shoulda/active_record/macros.rb +512 -0
  26. data/lib/shoulda/active_record/matchers.rb +43 -0
  27. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  28. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  29. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  30. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  31. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  32. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  33. data/lib/shoulda/active_record/matchers/have_db_index_matcher.rb +112 -0
  34. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +128 -0
  35. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  36. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  37. data/lib/shoulda/active_record/matchers/validate_format_of_matcher.rb +67 -0
  38. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  39. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  40. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  41. data/lib/shoulda/active_record/matchers/validation_matcher.rb +57 -0
  42. data/lib/shoulda/assertions.rb +71 -0
  43. data/lib/shoulda/autoload_macros.rb +46 -0
  44. data/lib/shoulda/context.rb +413 -0
  45. data/lib/shoulda/helpers.rb +8 -0
  46. data/lib/shoulda/macros.rb +133 -0
  47. data/lib/shoulda/minitest.rb +24 -0
  48. data/lib/shoulda/private_helpers.rb +13 -0
  49. data/lib/shoulda/proc_extensions.rb +14 -0
  50. data/lib/shoulda/rails.rb +13 -0
  51. data/lib/shoulda/rspec.rb +11 -0
  52. data/lib/shoulda/tasks.rb +3 -0
  53. data/lib/shoulda/tasks/list_tests.rake +29 -0
  54. data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
  55. data/lib/shoulda/test_unit.rb +22 -0
  56. data/rails/init.rb +7 -0
  57. data/test/README +36 -0
  58. data/test/fail_macros.rb +39 -0
  59. data/test/fixtures/addresses.yml +3 -0
  60. data/test/fixtures/friendships.yml +0 -0
  61. data/test/fixtures/posts.yml +5 -0
  62. data/test/fixtures/products.yml +0 -0
  63. data/test/fixtures/taggings.yml +0 -0
  64. data/test/fixtures/tags.yml +9 -0
  65. data/test/fixtures/users.yml +6 -0
  66. data/test/functional/posts_controller_test.rb +121 -0
  67. data/test/functional/users_controller_test.rb +19 -0
  68. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
  69. data/test/matchers/active_record/allow_value_matcher_test.rb +64 -0
  70. data/test/matchers/active_record/association_matcher_test.rb +263 -0
  71. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
  72. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  73. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  74. data/test/matchers/active_record/have_db_index_matcher_test.rb +91 -0
  75. data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
  76. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  77. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  78. data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
  79. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  80. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  81. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  82. data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
  83. data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
  84. data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
  85. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +32 -0
  86. data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
  87. data/test/matchers/controller/route_matcher_test.rb +58 -0
  88. data/test/matchers/controller/set_session_matcher_test.rb +38 -0
  89. data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
  90. data/test/model_builder.rb +106 -0
  91. data/test/other/autoload_macro_test.rb +18 -0
  92. data/test/other/context_test.rb +203 -0
  93. data/test/other/convert_to_should_syntax_test.rb +63 -0
  94. data/test/other/helpers_test.rb +340 -0
  95. data/test/other/private_helpers_test.rb +32 -0
  96. data/test/other/should_test.rb +271 -0
  97. data/test/rails_root/app/controllers/application_controller.rb +25 -0
  98. data/test/rails_root/app/controllers/posts_controller.rb +87 -0
  99. data/test/rails_root/app/controllers/users_controller.rb +84 -0
  100. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  101. data/test/rails_root/app/helpers/posts_helper.rb +2 -0
  102. data/test/rails_root/app/helpers/users_helper.rb +2 -0
  103. data/test/rails_root/app/models/address.rb +7 -0
  104. data/test/rails_root/app/models/flea.rb +3 -0
  105. data/test/rails_root/app/models/friendship.rb +4 -0
  106. data/test/rails_root/app/models/pets/cat.rb +7 -0
  107. data/test/rails_root/app/models/pets/dog.rb +10 -0
  108. data/test/rails_root/app/models/post.rb +12 -0
  109. data/test/rails_root/app/models/product.rb +12 -0
  110. data/test/rails_root/app/models/profile.rb +2 -0
  111. data/test/rails_root/app/models/registration.rb +2 -0
  112. data/test/rails_root/app/models/tag.rb +8 -0
  113. data/test/rails_root/app/models/tagging.rb +4 -0
  114. data/test/rails_root/app/models/treat.rb +3 -0
  115. data/test/rails_root/app/models/user.rb +32 -0
  116. data/test/rails_root/app/views/layouts/posts.rhtml +19 -0
  117. data/test/rails_root/app/views/layouts/users.rhtml +17 -0
  118. data/test/rails_root/app/views/layouts/wide.html.erb +1 -0
  119. data/test/rails_root/app/views/posts/edit.rhtml +27 -0
  120. data/test/rails_root/app/views/posts/index.rhtml +25 -0
  121. data/test/rails_root/app/views/posts/new.rhtml +26 -0
  122. data/test/rails_root/app/views/posts/show.rhtml +18 -0
  123. data/test/rails_root/app/views/users/edit.rhtml +22 -0
  124. data/test/rails_root/app/views/users/index.rhtml +22 -0
  125. data/test/rails_root/app/views/users/new.rhtml +21 -0
  126. data/test/rails_root/app/views/users/show.rhtml +13 -0
  127. data/test/rails_root/config/boot.rb +110 -0
  128. data/test/rails_root/config/database.yml +4 -0
  129. data/test/rails_root/config/environment.rb +18 -0
  130. data/test/rails_root/config/environments/test.rb +0 -0
  131. data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
  132. data/test/rails_root/config/initializers/shoulda.rb +8 -0
  133. data/test/rails_root/config/routes.rb +6 -0
  134. data/test/rails_root/db/migrate/001_create_users.rb +19 -0
  135. data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
  136. data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
  137. data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
  138. data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
  139. data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
  140. data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
  141. data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  142. data/test/rails_root/db/migrate/009_create_products.rb +17 -0
  143. data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
  144. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  145. data/test/rails_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  146. data/test/rails_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  147. data/test/rails_root/db/migrate/20090513104502_create_cats.rb +12 -0
  148. data/test/rails_root/db/schema.rb +0 -0
  149. data/test/rails_root/log/test.log +1 -0
  150. data/test/rails_root/public/404.html +30 -0
  151. data/test/rails_root/public/422.html +30 -0
  152. data/test/rails_root/public/500.html +30 -0
  153. data/test/rails_root/script/console +3 -0
  154. data/test/rails_root/script/generate +3 -0
  155. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  156. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  157. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  158. data/test/rspec_test.rb +207 -0
  159. data/test/test_helper.rb +28 -0
  160. data/test/unit/address_test.rb +10 -0
  161. data/test/unit/cat_test.rb +7 -0
  162. data/test/unit/dog_test.rb +9 -0
  163. data/test/unit/flea_test.rb +6 -0
  164. data/test/unit/friendship_test.rb +6 -0
  165. data/test/unit/post_test.rb +19 -0
  166. data/test/unit/product_test.rb +23 -0
  167. data/test/unit/tag_test.rb +15 -0
  168. data/test/unit/tagging_test.rb +6 -0
  169. data/test/unit/user_test.rb +80 -0
  170. 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