shoulda-context 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ We're using GitHub[http://github.com/thoughtbot/shoulda-context], and we've been getting any combination of github pull requests, tickets, patches, emails, etc. We need to normalize this workflow to make sure we don't miss any fixes.
2
+
3
+ * Make sure you're accessing the source from the {official repository}[http://github.com/thoughtbot/shoulda-context].
4
+ * We prefer git branches over patches, but we can take either.
5
+ * 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.
6
+ * If you're submitting patches, please cut each fix or feature into a separate patch.
7
+ * There should be an issue[http://github.com/thoughtbot/shoulda-context/issues] for any submission. If you've found a bug and want to fix it, open a new ticket at the same time.
8
+ * Please <b>don't send pull requests</b> Just update the issue 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.
9
+ * Contributions without tests won't be accepted. The file <tt>/test/README</tt> explains the testing system pretty thoroughly.
10
+
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'mocha'
4
+ gem 'ruby-debug'
5
+
@@ -0,0 +1,20 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ columnize (0.3.2)
5
+ linecache (0.43)
6
+ mocha (0.9.10)
7
+ rake
8
+ rake (0.8.7)
9
+ ruby-debug (0.10.4)
10
+ columnize (>= 0.1)
11
+ ruby-debug-base (~> 0.10.4.0)
12
+ ruby-debug-base (0.10.4)
13
+ linecache (>= 0.3)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ mocha
20
+ ruby-debug
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ = shoulda-context
2
+
3
+ {Official Documentation}[http://rubydoc.info/github/thoughtbot/shoulda-context/master/frames]
4
+
5
+ Shoulda's contexts make it easy to write understandable and maintainable tests for Test::Unit.
6
+ It's fully compatible with your existing tests in Test::Unit, and requires no retooling to use.
7
+
8
+ Refer to the {shoulda}[https://github.com/thoughtbot/shoulda] gem if you want to know more
9
+ about using shoulda with Rails or RSpec.
10
+
11
+ == Contexts
12
+
13
+ Instead of writing Ruby methods with lots_of_underscores, shoulda-context adds
14
+ context, setup, and should blocks...
15
+
16
+ class CalculatorTest < Test::Unit::TestCase
17
+ context "a calculator" do
18
+ setup do
19
+ @calculator = Calculator.new
20
+ end
21
+
22
+ should "add two numbers for the sum" do
23
+ assert_equal 4, @calculator.sum(2, 2)
24
+ end
25
+
26
+ should "multiply two numbers for the product" do
27
+ assert_equal 10, @calculator.product(2, 5)
28
+ end
29
+ end
30
+ end
31
+
32
+ ... which combine to produce the following test methods:
33
+
34
+ "test: A User instance should return its full name."
35
+ "test: A User instance with a profile should return true when sent #has_profile?."
36
+
37
+ == Assertions
38
+
39
+ It also has two additional Test::Unit assertions for working with Ruby's Array:
40
+
41
+ assert_same_elements([:a, :b, :c], [:c, :a, :b])
42
+ assert_contains(['a', '1'], /\d/)
43
+ assert_contains(['a', '1'], 'a')
44
+
45
+ = Credits
46
+
47
+ Shoulda is maintained and funded by {thoughtbot}[http://thoughtbot.com/community].
48
+ Thank you to all the {contributors}[https://github.com/thoughtbot/shoulda-context/contributors].
49
+
50
+ = License
51
+
52
+ Shoulda is Copyright © 2006-2010 thoughtbot, inc.
53
+ It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/gempackagetask'
7
+
8
+ $LOAD_PATH.unshift("lib")
9
+ load 'tasks/shoulda.rake'
10
+
11
+ test_files_pattern = 'test/**/*_test.rb'
12
+ Rake::TestTask.new do |t|
13
+ t.libs << 'lib' << 'test'
14
+ t.pattern = test_files_pattern
15
+ t.verbose = false
16
+ end
17
+
18
+ Rake::RDocTask.new { |rdoc|
19
+ rdoc.rdoc_dir = 'doc'
20
+ rdoc.title = "shoulda-context -- Context framework for Test::Unit"
21
+ rdoc.options << '--line-numbers'
22
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
23
+ rdoc.rdoc_files.include('README.rdoc', 'CONTRIBUTION_GUIDELINES.rdoc', 'lib/**/*.rb')
24
+ }
25
+
26
+ desc "Run code-coverage analysis using rcov"
27
+ task :coverage do
28
+ rm_rf "coverage"
29
+ files = Dir[test_files_pattern]
30
+ system "rcov --rails --sort coverage -Ilib #{files.join(' ')}"
31
+ end
32
+
33
+ eval("$specification = begin; #{IO.read('shoulda-context.gemspec')}; end")
34
+ Rake::GemPackageTask.new $specification do |pkg|
35
+ pkg.need_tar = true
36
+ pkg.need_zip = true
37
+ end
38
+
39
+ desc "Clean files generated by rake tasks"
40
+ task :clobber => [:clobber_rdoc, :clobber_package]
41
+
42
+ desc 'Default: run tests'
43
+ task :default => [:test]
44
+
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+
5
+ TMP = Dir::tmpdir
6
+
7
+ def usage(msg = nil)
8
+ puts "Error: #{msg}" if msg
9
+ puts if msg
10
+ puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
11
+ puts
12
+ puts "Will convert an existing test file with names like "
13
+ puts
14
+ puts " def test_should_do_stuff"
15
+ puts " ..."
16
+ puts " end"
17
+ puts
18
+ puts "to one using the new syntax: "
19
+ puts
20
+ puts " should \"be super cool\" do"
21
+ puts " ..."
22
+ puts " end"
23
+ puts
24
+ puts "A copy of the old file will be left under #{TMP} in case\nthis script just seriously screws up"
25
+ puts
26
+ exit (msg ? 2 : 0)
27
+ end
28
+
29
+ usage("Wrong number of arguments.") unless ARGV.size == 1
30
+ usage("Temp directory '#{TMP}' is not valid. Set TMPDIR environment variable to a writeable directory.") unless File.directory?(TMP) && File.writable?(TMP)
31
+
32
+ file = ARGV.shift
33
+ tmpfile = File.join(TMP, File.basename(file))
34
+ usage("File '#{file}' doesn't exist") unless File.exists?(file)
35
+
36
+ FileUtils.cp(file, tmpfile)
37
+ contents = File.read(tmpfile)
38
+ contents.gsub!(/def test_should_(\S+)/) {|line| "should \"#{$1.tr('_', ' ')}\" do"}
39
+ contents.gsub!(/def test_(\S+)/) {|line| "should \"RENAME ME: test #{$1.tr('_', ' ')}\" do"}
40
+ File.open(file, 'w') { |f| f.write(contents) }
41
+
42
+ puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"
@@ -0,0 +1,12 @@
1
+ require 'test/unit'
2
+ require 'shoulda/context/version'
3
+ require 'shoulda/context/proc_extensions'
4
+ require 'shoulda/context/assertions'
5
+ require 'shoulda/context/context'
6
+ require 'shoulda/context/autoload_macros'
7
+
8
+ class Test::Unit::TestCase
9
+ include Shoulda::Context::Assertions
10
+ include Shoulda::Context::InstanceMethods
11
+ extend Shoulda::Context::ClassMethods
12
+ end
@@ -0,0 +1,81 @@
1
+ module Shoulda # :nodoc:
2
+ module Context
3
+ module Assertions
4
+ # Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
5
+ #
6
+ # assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
7
+ def assert_same_elements(a1, a2, msg = nil)
8
+ [:select, :inject, :size].each do |m|
9
+ [a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
10
+ end
11
+
12
+ assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
13
+ assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
14
+
15
+ assert_equal(a1h, a2h, msg)
16
+ end
17
+
18
+ # Asserts that the given collection contains item x. If x is a regular expression, ensure that
19
+ # at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
20
+ #
21
+ # assert_contains(['a', '1'], /\d/) => passes
22
+ # assert_contains(['a', '1'], 'a') => passes
23
+ # assert_contains(['a', '1'], /not there/) => fails
24
+ def assert_contains(collection, x, extra_msg = "")
25
+ collection = [collection] unless collection.is_a?(Array)
26
+ msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
27
+ case x
28
+ when Regexp
29
+ assert(collection.detect { |e| e =~ x }, msg)
30
+ else
31
+ assert(collection.include?(x), msg)
32
+ end
33
+ end
34
+
35
+ # Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
36
+ # none of the elements from the collection match x.
37
+ def assert_does_not_contain(collection, x, extra_msg = "")
38
+ collection = [collection] unless collection.is_a?(Array)
39
+ msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg
40
+ case x
41
+ when Regexp
42
+ assert(!collection.detect { |e| e =~ x }, msg)
43
+ else
44
+ assert(!collection.include?(x), msg)
45
+ end
46
+ end
47
+
48
+ # Asserts that the given matcher returns true when +target+ is passed to #matches?
49
+ def assert_accepts(matcher, target, options = {})
50
+ if matcher.respond_to?(:in_context)
51
+ matcher.in_context(self)
52
+ end
53
+
54
+ if matcher.matches?(target)
55
+ assert_block { true }
56
+ if options[:message]
57
+ assert_match options[:message], matcher.negative_failure_message
58
+ end
59
+ else
60
+ assert_block(matcher.failure_message) { false }
61
+ end
62
+ end
63
+
64
+ # Asserts that the given matcher returns false when +target+ is passed to #matches?
65
+ def assert_rejects(matcher, target, options = {})
66
+ if matcher.respond_to?(:in_context)
67
+ matcher.in_context(self)
68
+ end
69
+
70
+ unless matcher.matches?(target)
71
+ assert_block { true }
72
+ if options[:message]
73
+ assert_match options[:message], matcher.failure_message
74
+ end
75
+ else
76
+ assert_block(matcher.negative_failure_message) { false }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ 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,446 @@
1
+ module Shoulda
2
+ module Context
3
+ class << self
4
+ attr_accessor :contexts
5
+ def contexts # :nodoc:
6
+ @contexts ||= []
7
+ end
8
+
9
+ def current_context # :nodoc:
10
+ self.contexts.last
11
+ end
12
+
13
+ def add_context(context) # :nodoc:
14
+ self.contexts.push(context)
15
+ end
16
+
17
+ def remove_context # :nodoc:
18
+ self.contexts.pop
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ # == Should statements
24
+ #
25
+ # Should statements are just syntactic sugar over normal Test::Unit test
26
+ # methods. A should block contains all the normal code and assertions
27
+ # you're used to seeing, with the added benefit that they can be wrapped
28
+ # inside context blocks (see below).
29
+ #
30
+ # === Example:
31
+ #
32
+ # class UserTest < Test::Unit::TestCase
33
+ #
34
+ # def setup
35
+ # @user = User.new("John", "Doe")
36
+ # end
37
+ #
38
+ # should "return its full name"
39
+ # assert_equal 'John Doe', @user.full_name
40
+ # end
41
+ #
42
+ # end
43
+ #
44
+ # ...will produce the following test:
45
+ # * <tt>"test: User should return its full name. "</tt>
46
+ #
47
+ # Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
48
+ #
49
+ # Should statements can also take a Proc as a <tt>:before </tt>option. This proc runs after any
50
+ # parent context's setups but before the current context's setup.
51
+ #
52
+ # === Example:
53
+ #
54
+ # context "Some context" do
55
+ # setup { puts("I run after the :before proc") }
56
+ #
57
+ # should "run a :before proc", :before => lambda { puts("I run before the setup") } do
58
+ # assert true
59
+ # end
60
+ # end
61
+ #
62
+ # Should statements can also wrap matchers, making virtually any matcher
63
+ # usable in a macro style. The matcher's description is used to generate a
64
+ # test name and failure message, and the test will pass if the matcher
65
+ # matches the subject.
66
+ #
67
+ # === Example:
68
+ #
69
+ # should validate_presence_of(:first_name).with_message(/gotta be there/)
70
+ #
71
+
72
+ def should(name_or_matcher, options = {}, &blk)
73
+ if Shoulda::Context.current_context
74
+ Shoulda::Context.current_context.should(name_or_matcher, options, &blk)
75
+ else
76
+ context_name = self.name.gsub(/Test/, "")
77
+ context = Shoulda::Context::Context.new(context_name, self) do
78
+ should(name_or_matcher, options, &blk)
79
+ end
80
+ context.build
81
+ end
82
+ end
83
+
84
+ # Allows negative tests using matchers. The matcher's description is used
85
+ # to generate a test name and negative failure message, and the test will
86
+ # pass unless the matcher matches the subject.
87
+ #
88
+ # === Example:
89
+ #
90
+ # should_not set_the_flash
91
+ def should_not(matcher)
92
+ if Shoulda::Context.current_context
93
+ Shoulda::Context.current_context.should_not(matcher)
94
+ else
95
+ context_name = self.name.gsub(/Test/, "")
96
+ context = Shoulda::Context::Context.new(context_name, self) do
97
+ should_not(matcher)
98
+ end
99
+ context.build
100
+ end
101
+ end
102
+
103
+ # == Before statements
104
+ #
105
+ # Before statements are should statements that run before the current
106
+ # context's setup. These are especially useful when setting expectations.
107
+ #
108
+ # === Example:
109
+ #
110
+ # class UserControllerTest < Test::Unit::TestCase
111
+ # context "the index action" do
112
+ # setup do
113
+ # @users = [Factory(:user)]
114
+ # User.stubs(:find).returns(@users)
115
+ # end
116
+ #
117
+ # context "on GET" do
118
+ # setup { get :index }
119
+ #
120
+ # should respond_with(:success)
121
+ #
122
+ # # runs before "get :index"
123
+ # before_should "find all users" do
124
+ # User.expects(:find).with(:all).returns(@users)
125
+ # end
126
+ # end
127
+ # end
128
+ # end
129
+ def before_should(name, &blk)
130
+ should(name, :before => blk) { assert true }
131
+ end
132
+
133
+ # Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
134
+ def should_eventually(name, options = {}, &blk)
135
+ context_name = self.name.gsub(/Test/, "")
136
+ context = Shoulda::Context::Context.new(context_name, self) do
137
+ should_eventually(name, &blk)
138
+ end
139
+ context.build
140
+ end
141
+
142
+ # == Contexts
143
+ #
144
+ # A context block groups should statements under a common set of setup/teardown methods.
145
+ # Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
146
+ # and readability of your test code.
147
+ #
148
+ # A context block can contain setup, should, should_eventually, and teardown blocks.
149
+ #
150
+ # class UserTest < Test::Unit::TestCase
151
+ # context "A User instance" do
152
+ # setup do
153
+ # @user = User.find(:first)
154
+ # end
155
+ #
156
+ # should "return its full name"
157
+ # assert_equal 'John Doe', @user.full_name
158
+ # end
159
+ # end
160
+ # end
161
+ #
162
+ # This code will produce the method <tt>"test: A User instance should return its full name. "</tt>.
163
+ #
164
+ # Contexts may be nested. Nested contexts run their setup blocks from out to in before each
165
+ # should statement. They then run their teardown blocks from in to out after each should statement.
166
+ #
167
+ # class UserTest < Test::Unit::TestCase
168
+ # context "A User instance" do
169
+ # setup do
170
+ # @user = User.find(:first)
171
+ # end
172
+ #
173
+ # should "return its full name"
174
+ # assert_equal 'John Doe', @user.full_name
175
+ # end
176
+ #
177
+ # context "with a profile" do
178
+ # setup do
179
+ # @user.profile = Profile.find(:first)
180
+ # end
181
+ #
182
+ # should "return true when sent :has_profile?"
183
+ # assert @user.has_profile?
184
+ # end
185
+ # end
186
+ # end
187
+ # end
188
+ #
189
+ # This code will produce the following methods
190
+ # * <tt>"test: A User instance should return its full name. "</tt>
191
+ # * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
192
+ #
193
+ # <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
194
+ # tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
195
+
196
+ def context(name, &blk)
197
+ if Shoulda::Context.current_context
198
+ Shoulda::Context.current_context.context(name, &blk)
199
+ else
200
+ context = Shoulda::Context::Context.new(name, self, &blk)
201
+ context.build
202
+ end
203
+ end
204
+
205
+ # Returns the class being tested, as determined by the test class name.
206
+ #
207
+ # class UserTest; described_type; end
208
+ # # => User
209
+ def described_type
210
+ @described_type ||= self.name.
211
+ gsub(/Test$/, '').
212
+ split('::').
213
+ inject(Object) { |parent, local_name| parent.const_get(local_name) }
214
+ end
215
+
216
+ # Sets the return value of the subject instance method:
217
+ #
218
+ # class UserTest < Test::Unit::TestCase
219
+ # subject { User.first }
220
+ #
221
+ # # uses the existing user
222
+ # should validate_uniqueness_of(:email)
223
+ # end
224
+ def subject(&block)
225
+ @subject_block = block
226
+ end
227
+
228
+ def subject_block # :nodoc:
229
+ @subject_block
230
+ end
231
+ end
232
+
233
+ module InstanceMethods
234
+ # Returns an instance of the class under test.
235
+ #
236
+ # class UserTest
237
+ # should "be a user" do
238
+ # assert_kind_of User, subject # passes
239
+ # end
240
+ # end
241
+ #
242
+ # The subject can be explicitly set using the subject class method:
243
+ #
244
+ # class UserTest
245
+ # subject { User.first }
246
+ # should "be an existing user" do
247
+ # assert !subject.new_record? # uses the first user
248
+ # end
249
+ # end
250
+ #
251
+ # The subject is used by all macros that require an instance of the class
252
+ # being tested.
253
+ def subject
254
+ @shoulda_subject ||= construct_subject
255
+ end
256
+
257
+ def subject_block # :nodoc:
258
+ (@shoulda_context && @shoulda_context.subject_block) || self.class.subject_block
259
+ end
260
+
261
+ def get_instance_of(object_or_klass) # :nodoc:
262
+ if object_or_klass.is_a?(Class)
263
+ object_or_klass.new
264
+ else
265
+ object_or_klass
266
+ end
267
+ end
268
+
269
+ def instance_variable_name_for(klass) # :nodoc:
270
+ klass.to_s.split('::').last.underscore
271
+ end
272
+
273
+ private
274
+
275
+ def construct_subject
276
+ if subject_block
277
+ instance_eval(&subject_block)
278
+ else
279
+ get_instance_of(self.class.described_type)
280
+ end
281
+ end
282
+ end
283
+
284
+ class Context # :nodoc:
285
+
286
+ attr_accessor :name # my name
287
+ attr_accessor :parent # may be another context, or the original test::unit class.
288
+ attr_accessor :subcontexts # array of contexts nested under myself
289
+ attr_accessor :setup_blocks # blocks given via setup methods
290
+ attr_accessor :teardown_blocks # blocks given via teardown methods
291
+ attr_accessor :shoulds # array of hashes representing the should statements
292
+ attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
293
+ attr_accessor :subject_block
294
+
295
+ def initialize(name, parent, &blk)
296
+ Shoulda::Context.add_context(self)
297
+ self.name = name
298
+ self.parent = parent
299
+ self.setup_blocks = []
300
+ self.teardown_blocks = []
301
+ self.shoulds = []
302
+ self.should_eventuallys = []
303
+ self.subcontexts = []
304
+
305
+ merge_block(&blk)
306
+ Shoulda::Context.remove_context
307
+ end
308
+
309
+ def merge_block(&blk)
310
+ blk.bind(self).call
311
+ end
312
+
313
+ def context(name, &blk)
314
+ self.subcontexts << Context.new(name, self, &blk)
315
+ end
316
+
317
+ def setup(&blk)
318
+ self.setup_blocks << blk
319
+ end
320
+
321
+ def teardown(&blk)
322
+ self.teardown_blocks << blk
323
+ end
324
+
325
+ def should(name_or_matcher, options = {}, &blk)
326
+ if name_or_matcher.respond_to?(:description) && name_or_matcher.respond_to?(:matches?)
327
+ name = name_or_matcher.description
328
+ blk = lambda { assert_accepts name_or_matcher, subject }
329
+ else
330
+ name = name_or_matcher
331
+ end
332
+
333
+ if blk
334
+ self.shoulds << { :name => name, :before => options[:before], :block => blk }
335
+ else
336
+ self.should_eventuallys << { :name => name }
337
+ end
338
+ end
339
+
340
+ def should_not(matcher)
341
+ name = matcher.description
342
+ blk = lambda { assert_rejects matcher, subject }
343
+ self.shoulds << { :name => "not #{name}", :block => blk }
344
+ end
345
+
346
+ def should_eventually(name, &blk)
347
+ self.should_eventuallys << { :name => name, :block => blk }
348
+ end
349
+
350
+ def subject(&block)
351
+ self.subject_block = block
352
+ end
353
+
354
+ def subject_block
355
+ return @subject_block if @subject_block
356
+ parent.subject_block
357
+ end
358
+
359
+ def full_name
360
+ parent_name = parent.full_name if am_subcontext?
361
+ return [parent_name, name].join(" ").strip
362
+ end
363
+
364
+ def am_subcontext?
365
+ parent.is_a?(self.class) # my parent is the same class as myself.
366
+ end
367
+
368
+ def test_unit_class
369
+ am_subcontext? ? parent.test_unit_class : parent
370
+ end
371
+
372
+ def test_methods
373
+ @test_methods ||= Hash.new { |h,k|
374
+ h[k] = Hash[k.instance_methods.map { |n| [n, true] }]
375
+ }
376
+ end
377
+
378
+ def create_test_from_should_hash(should)
379
+ test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
380
+
381
+ if test_methods[test_unit_class][test_name.to_s] then
382
+ warn " * WARNING: '#{test_name}' is already defined"
383
+ end
384
+
385
+ test_methods[test_unit_class][test_name.to_s] = true
386
+
387
+ context = self
388
+ test_unit_class.send(:define_method, test_name) do
389
+ @shoulda_context = context
390
+ begin
391
+ context.run_parent_setup_blocks(self)
392
+ should[:before].bind(self).call if should[:before]
393
+ context.run_current_setup_blocks(self)
394
+ should[:block].bind(self).call
395
+ ensure
396
+ context.run_all_teardown_blocks(self)
397
+ end
398
+ end
399
+ end
400
+
401
+ def run_all_setup_blocks(binding)
402
+ run_parent_setup_blocks(binding)
403
+ run_current_setup_blocks(binding)
404
+ end
405
+
406
+ def run_parent_setup_blocks(binding)
407
+ self.parent.run_all_setup_blocks(binding) if am_subcontext?
408
+ end
409
+
410
+ def run_current_setup_blocks(binding)
411
+ setup_blocks.each do |setup_block|
412
+ setup_block.bind(binding).call
413
+ end
414
+ end
415
+
416
+ def run_all_teardown_blocks(binding)
417
+ teardown_blocks.reverse.each do |teardown_block|
418
+ teardown_block.bind(binding).call
419
+ end
420
+ self.parent.run_all_teardown_blocks(binding) if am_subcontext?
421
+ end
422
+
423
+ def print_should_eventuallys
424
+ should_eventuallys.each do |should|
425
+ test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
426
+ puts " * DEFERRED: " + test_name
427
+ end
428
+ end
429
+
430
+ def build
431
+ shoulds.each do |should|
432
+ create_test_from_should_hash(should)
433
+ end
434
+
435
+ subcontexts.each { |context| context.build }
436
+
437
+ print_should_eventuallys
438
+ end
439
+
440
+ def method_missing(method, *args, &blk)
441
+ test_unit_class.send(method, *args, &blk)
442
+ end
443
+
444
+ end
445
+ end
446
+ end