public
Rubygem
Description: Makes tests easy on the fingers and the eyes
Homepage: http://www.thoughtbot.com/projects/shoulda
Clone URL: git://github.com/thoughtbot/shoulda.git
updated shoulda gem to refactored version

git-svn-id: https://svn.thoughtbot.com/plugins/shoulda/trunk@368 
7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
tsaleh (author)
Thu Feb 28 11:32:03 -0800 2008
commit  6b91e8b325ef8a1105f250dfa9ee363b3e4d482f
tree    dc2a5f7f7e6bbd2108f333de550594da9f4da4fa
parent  4f4e52f6d4600a17900583695f3e843a2a5b4c36
...
1
2
3
4
5
6
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
9
 
10
11
12
13
14
15
16
 
17
18
19
...
24
25
26
27
 
28
29
30
 
 
31
32
33
 
34
35
36
...
52
53
54
55
56
57
58
59
 
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
107
108
109
110
111
112
113
114
115
116
117
118
119
 
 
 
 
120
121
122
123
 
 
 
124
125
126
127
128
129
130
131
132
 
 
 
 
133
134
135
136
 
137
138
139
140
141
142
143
 
 
 
 
 
 
 
 
144
145
146
147
148
149
150
151
 
 
152
 
153
154
155
...
157
158
159
160
161
162
 
163
164
165
 
...
1
2
3
 
 
 
 
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 
59
60
61
62
63
64
65
 
66
67
68
69
...
74
75
76
 
77
78
 
 
79
80
81
82
 
83
84
85
86
...
102
103
104
 
 
 
 
 
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
 
 
 
 
 
 
 
 
 
 
197
198
199
200
201
 
 
 
202
203
204
205
206
 
 
 
 
 
 
 
207
208
209
210
211
 
 
 
212
213
214
 
 
 
 
 
215
216
217
218
219
220
221
222
223
224
 
 
 
 
 
 
225
226
227
228
229
230
231
...
233
234
235
 
 
 
236
237
238
239
240
0
@@ -1,19 +1,69 @@
0
 require File.join(File.dirname(__FILE__), 'proc_extensions')
0
 
0
 module Thoughtbot
0
- class Shoulda
0
- VERSION = '1.0.0'
0
-
0
- # = context and should blocks
0
+ module Shoulda
0
+ class << self
0
+ attr_accessor :current_context
0
+ end
0
+
0
+ VERSION = '1.1.0'
0
+
0
+ # = Should statements
0
+ #
0
+ # Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
0
+ # contains all the normal code and assertions you're used to seeing, with the added benefit that
0
+ # they can be wrapped inside context blocks (see below).
0
+ #
0
+ # == Example:
0
+ #
0
+ # class UserTest << Test::Unit::TestCase
0
+ #
0
+ # def setup
0
+ # @user = User.new("John", "Doe")
0
+ # end
0
+ #
0
+ # should "return its full name"
0
+ # assert_equal 'John Doe', @user.full_name
0
+ # end
0
+ #
0
+ # end
0
+ #
0
+ # ...will produce the following test:
0
+ # * <tt>"test: User should return its full name. "</tt>
0
+ #
0
+ # Note: The part before <tt>should</tt> in the test name is gleamed from the name of the Test::Unit class.
0
+
0
+ def should(name, &blk)
0
+ if Shoulda.current_context
0
+ Shoulda.current_context.should(name, &blk)
0
+ else
0
+ context_name = self.name.gsub(/Test/, "")
0
+ context = Thoughtbot::Shoulda::Context.new(context_name, self) do
0
+ should(name, &blk)
0
+ end
0
+ context.build
0
+ end
0
+ end
0
+
0
+ # Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.
0
+ def should_eventually(name, &blk)
0
+ context_name = self.name.gsub(/Test/, "")
0
+ context = Thoughtbot::Shoulda::Context.new(context_name, self) do
0
+ should_eventually(name, &blk)
0
+ end
0
+ context.build
0
+ end
0
+
0
+ # = Contexts
0
     #
0
- # A context block groups should statements under a common setup/teardown method.
0
+ # A context block groups should statements under a common set of setup/teardown methods.
0
     # Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
0
     # and readability of your test code.
0
     #
0
     # A context block can contain setup, should, should_eventually, and teardown blocks.
0
     #
0
     # class UserTest << Test::Unit::TestCase
0
- # context "a User instance" do
0
+ # context "A User instance" do
0
     # setup do
0
     # @user = User.find(:first)
0
     # end
0
@@ -24,13 +74,13 @@ module Thoughtbot
0
     # end
0
     # end
0
     #
0
- # This code will produce the method <tt>"test a User instance should return its full name"</tt>.
0
+ # This code will produce the method <tt>"test: A User instance should return its full name. "</tt>.
0
     #
0
- # Contexts may be nested. Nested contexts run their setup blocks from out to in before each test.
0
- # They then run their teardown blocks from in to out after each test.
0
+ # Contexts may be nested. Nested contexts run their setup blocks from out to in before each
0
+ # should statement. They then run their teardown blocks from in to out after each should statement.
0
     #
0
     # class UserTest << Test::Unit::TestCase
0
- # context "a User instance" do
0
+ # context "A User instance" do
0
     # setup do
0
     # @user = User.find(:first)
0
     # end
0
@@ -52,104 +102,130 @@ module Thoughtbot
0
     # end
0
     #
0
     # This code will produce the following methods
0
- # * <tt>"test: a User instance should return its full name."</tt>
0
- # * <tt>"test: a User instance with a profile should return true when sent :has_profile?."</tt>
0
- #
0
- # <b>A context block can exist next to normal <tt>def test_the_old_way; end</tt> tests</b>,
0
- # meaning you do not have to fully commit to the context/should syntax in a test file.
0
+ # * <tt>"test: A User instance should return its full name. "</tt>
0
+ # * <tt>"test: A User instance with a profile should return true when sent :has_profile?. "</tt>
0
     #
0
+ # <b>Just like should statements, a context block can exist next to normal <tt>def test_the_old_way; end</tt>
0
+ # tests</b>. This means you do not have to fully commit to the context/should syntax in a test file.
0
+
0
+ def context(name, &blk)
0
+ if Shoulda.current_context
0
+ Shoulda.current_context.context(name, &blk)
0
+ else
0
+ context = Thoughtbot::Shoulda::Context.new(name, self, &blk)
0
+ context.build
0
+ end
0
+ end
0
+
0
+ class Context # :nodoc:
0
+
0
+ attr_accessor :name # my name
0
+ attr_accessor :parent # may be another context, or the original test::unit class.
0
+ attr_accessor :subcontexts # array of contexts nested under myself
0
+ attr_accessor :setup_block # block given via a setup method
0
+ attr_accessor :teardown_block # block given via a teardown method
0
+ attr_accessor :shoulds # array of hashes representing the should statements
0
+ attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
0
+
0
+ def initialize(name, parent, &blk)
0
+ Shoulda.current_context = self
0
+ self.name = name
0
+ self.parent = parent
0
+ self.setup_block = nil
0
+ self.teardown_block = nil
0
+ self.shoulds = []
0
+ self.should_eventuallys = []
0
+ self.subcontexts = []
0
 
0
- module ClassMethods
0
- def self.included(other) # :nodoc:
0
- @@context_names = []
0
- @@setup_blocks = []
0
- @@teardown_blocks = []
0
- end
0
-
0
- # Defines a test method. Can be called either inside our outside of a context.
0
- # Optionally specify <tt>:unimplimented => true</tt> (see should_eventually).
0
- #
0
- # Example:
0
- #
0
- # class UserTest << Test::Unit::TestCase
0
- # should "return first user on find(:first)"
0
- # assert_equal users(:first), User.find(:first)
0
- # end
0
- # end
0
- #
0
- # Would create a test named
0
- # 'test: should return first user on find(:first)'
0
- #
0
- def should(name, opts = {}, &should_block)
0
- test_name = ["test:", @@context_names, "should", "#{name}. "].flatten.join(' ').to_sym
0
-
0
- name_defined = eval("self.instance_methods.include?('#{test_name.to_s.gsub(/['"]/, '\$1')}')", should_block.binding)
0
- raise ArgumentError, "'#{test_name}' is already defined" and return if name_defined
0
-
0
- setup_blocks = @@setup_blocks.dup
0
- teardown_blocks = @@teardown_blocks.dup
0
-
0
- if opts[:unimplemented]
0
- define_method test_name do |*args|
0
- # XXX find a better way of doing this.
0
- assert true
0
- STDOUT.putc "X" # Tests for this model are missing.
0
- end
0
- else
0
- define_method test_name do |*args|
0
- begin
0
- setup_blocks.each {|b| b.bind(self).call }
0
- should_block.bind(self).call(*args)
0
- ensure
0
- teardown_blocks.reverse.each {|b| b.bind(self).call }
0
- end
0
+ blk.bind(self).call
0
+ Shoulda.current_context = nil
0
+ end
0
+
0
+ def context(name, &blk)
0
+ subcontexts << Context.new(name, self, &blk)
0
+ Shoulda.current_context = self
0
+ end
0
+
0
+ def setup(&blk)
0
+ self.setup_block = blk
0
+ end
0
+
0
+ def teardown(&blk)
0
+ self.teardown_block = blk
0
+ end
0
+
0
+ def should(name, &blk)
0
+ self.shoulds << { :name => name, :block => blk }
0
+ end
0
+
0
+ def should_eventually(name, &blk)
0
+ self.should_eventuallys << { :name => name, :block => blk }
0
+ end
0
+
0
+ def full_name
0
+ parent_name = parent.full_name if am_subcontext?
0
+ return [parent_name, name].join(" ").strip
0
+ end
0
+
0
+ def am_subcontext?
0
+ parent.is_a?(self.class) # my parent is the same class as myself.
0
+ end
0
+
0
+ def test_unit_class
0
+ am_subcontext? ? parent.test_unit_class : parent
0
+ end
0
+
0
+ def create_test_from_should_hash(should)
0
+ test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
0
+
0
+ if test_unit_class.instance_methods.include?(test_name.to_s)
0
+ puts "'#{test_name}' is already defined"
0
+ #raise ArgumentError, "'#{test_name}' is already defined"
0
+ end
0
+
0
+ context = self
0
+ test_unit_class.send(:define_method, test_name) do |*args|
0
+ begin
0
+ context.run_all_setup_blocks(self)
0
+ should[:block].bind(self).call
0
+ ensure
0
+ context.run_all_teardown_blocks(self)
0
           end
0
         end
0
       end
0
 
0
- # Creates a context block with the given name.
0
- def context(name, &context_block)
0
- saved_setups = @@setup_blocks.dup
0
- saved_teardowns = @@teardown_blocks.dup
0
- saved_contexts = @@context_names.dup
0
-
0
- @@setup_defined = false
0
-
0
- @@context_names << name
0
- context_block.bind(self).call
0
+ def run_all_setup_blocks(binding)
0
+ self.parent.run_all_setup_blocks(binding) if am_subcontext?
0
+ setup_block.bind(binding).call if setup_block
0
+ end
0
 
0
- @@context_names = saved_contexts
0
- @@setup_blocks = saved_setups
0
- @@teardown_blocks = saved_teardowns
0
+ def run_all_teardown_blocks(binding)
0
+ teardown_block.bind(binding).call if teardown_block
0
+ self.parent.run_all_teardown_blocks(binding) if am_subcontext?
0
       end
0
 
0
- # Run before every should block in the current context.
0
- # If a setup block appears in a nested context, it will be run after the setup blocks
0
- # in the parent contexts.
0
- def setup(&setup_block)
0
- if @@setup_defined
0
- raise RuntimeError, "Either you have two setup blocks in one context, " +
0
- "or a setup block outside of a context. Both are equally bad."
0
+ def print_should_eventuallys
0
+ should_eventuallys.each do |should|
0
+ test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
0
+ puts " * DEFERRED: " + test_name
0
         end
0
- @@setup_defined = true
0
-
0
- @@setup_blocks << setup_block
0
+ subcontexts.each { |context| context.print_should_eventuallys }
0
       end
0
 
0
- # Run after every should block in the current context.
0
- # If a teardown block appears in a nested context, it will be run before the teardown
0
- # blocks in the parent contexts.
0
- def teardown(&teardown_block)
0
- @@teardown_blocks << teardown_block
0
+ def build
0
+ shoulds.each do |should|
0
+ create_test_from_should_hash(should)
0
+ end
0
+
0
+ subcontexts.each { |context| context.build }
0
+
0
+ print_should_eventuallys
0
       end
0
 
0
- # Defines a specification that is not yet implemented.
0
- # Will be displayed as an 'X' when running tests, and failures will not be shown.
0
- # This is equivalent to:
0
- # should(name, {:unimplemented => true}, &block)
0
- def should_eventually(name, &block)
0
- should("eventually #{name}", {:unimplemented => true}, &block)
0
+ def method_missing(method, *args, &blk)
0
+ test_unit_class.send(method, *args, &blk)
0
       end
0
+
0
     end
0
   end
0
 end
0
@@ -157,9 +233,8 @@ end
0
 module Test # :nodoc: all
0
   module Unit
0
     class TestCase
0
- class << self
0
- include Thoughtbot::Shoulda::ClassMethods
0
- end
0
+ extend Thoughtbot::Shoulda
0
     end
0
   end
0
 end
0
+

Comments

    No one has commented yet.