public
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
Search Repo:
Moved everthing to trunk

git-svn-id: https://svn.thoughtbot.com/plugins/tb_test_helpers/trunk@38 
7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
tsaleh (author)
Wed Mar 14 11:12:55 -0700 2007
commit  7a4202f9b8fa77318694864af9d1bcfde4084deb
tree    e348afe2b4ed7eb79123659f2e034cb4e5a888b6
parent  37686ae0dadb3a820db039a9ab3d024983034e7f
0
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
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
0
@@ -0,0 +1,40 @@
0
+#!/usr/bin/env ruby
0
+require 'fileutils'
0
+
0
+def usage(msg = nil)
0
+ puts "Error: #{msg}" if msg
0
+ puts if msg
0
+ puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
0
+ puts
0
+ puts "Will convert an existing test file with names like "
0
+ puts
0
+ puts " def test_should_do_stuff"
0
+ puts " ..."
0
+ puts " end"
0
+ puts
0
+ puts "to one using the new syntax: "
0
+ puts
0
+ puts " should \"be super cool\" do"
0
+ puts " ..."
0
+ puts " end"
0
+ puts
0
+ puts "A copy of the old file will be left under /tmp/ in case this script just seriously screws up"
0
+ puts
0
+ exit (msg ? 2 : 0)
0
+end
0
+
0
+usage("Wrong number of arguments.") unless ARGV.size == 1
0
+usage("This system doesn't have a /tmp directory. wtf?") unless File.directory?('/tmp')
0
+
0
+file = ARGV.shift
0
+tmpfile = "/tmp/#{File.basename(file)}"
0
+usage("File '#{file}' doesn't exist") unless File.exists?(file)
0
+
0
+FileUtils.cp(file, tmpfile)
0
+contents = File.read(tmpfile)
0
+contents.gsub!(/def test_should_(.*)\s*$/, 'should "\1" do')
0
+contents.gsub!(/def test_(.*)\s*$/, 'should "RENAME ME: test \1" do')
0
+contents.gsub!(/should ".*" do$/) {|line| line.tr!('_', ' ')}
0
+File.open(file, 'w') { |f| f.write(contents) }
0
+
0
+puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"
...
 
0
...
1
2
0
@@ -0,0 +1 @@
0
+require 'tb_test_helpers'
0
\ No newline at end of file
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
...
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
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
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
0
@@ -0,0 +1,198 @@
0
+class Test::Unit::TestCase
0
+ class << self
0
+
0
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
0
+ # Requires an existing record
0
+ def should_require_attributes(*attributes)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ attributes.each do |attribute|
0
+ should "require #{attribute} to be set" do
0
+ object = klass.new
0
+ assert !object.valid?, "Instance is still valid"
0
+ assert object.errors.on(attribute), "No errors found"
0
+ assert object.errors.on(attribute).to_a.include?("can't be blank"), "Error message doesn't match"
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
0
+ # Requires an existing record
0
+ def should_require_unique_attributes(*attributes)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ attributes.each do |attribute|
0
+ attribute = attribute.to_sym
0
+ should "require unique value for #{attribute}" do
0
+ assert existing = klass.find(:first), "Can't find first #{klass}"
0
+ object = klass.new
0
+ object.send(:"#{attribute}=", existing.send(attribute))
0
+ assert !object.valid?, "Instance is still valid"
0
+ assert object.errors.on(attribute), "No errors found"
0
+ assert object.errors.on(attribute).to_a.include?('has already been taken'), "Error message doesn't match"
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the attribute cannot be set on update
0
+ # Requires an existing record
0
+ def should_protect_attributes(*attributes)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ attributes.each do |attribute|
0
+ attribute = attribute.to_sym
0
+ should "not allow #{attribute} to be changed by update" do
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ value = object[attribute]
0
+ assert object.update_attributes({ attribute => 1 }),
0
+ "Cannot update #{klass} with { :#{attribute} => 1 }, #{object.errors.full_messages.to_sentence}"
0
+ assert object.valid?, "#{klass} isn't valid after changing #{attribute}"
0
+ assert_equal value, object[attribute], "Was able to change #{klass}##{attribute}"
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the attribute cannot be set to the given values
0
+ # Requires an existing record
0
+ def should_not_allow_values_for(attribute, *bad_values)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ bad_values.each do |v|
0
+ should "not allow #{attribute} to be set to \"#{v}\"" do
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send("#{attribute}=", v)
0
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
0
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
0
+ assert_match(/invalid/, object.errors.on(attribute), "Error set on #{attribute} doesn't include \"invalid\" when set to \"#{v}\"")
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the attribute can be set to the given values.
0
+ # Requires an existing record
0
+ def should_allow_values_for(attribute, *good_values)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ good_values.each do |v|
0
+ should "allow #{attribute} to be set to \"#{v}\"" do
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send("#{attribute}=", v)
0
+ object.save
0
+ # assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
0
+ assert_no_match(/invalid/, object.errors.on(attribute), "Error set on #{attribute} includes \"invalid\" when set to \"#{v}\"")
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the length of the attribute is in the given range
0
+ # Requires an existing record
0
+ def should_ensure_length_in_range(attribute, range)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ min_length = range.first
0
+ max_length = range.last
0
+
0
+ min_value = "x" * (min_length - 1)
0
+ max_value = "x" * (max_length + 1)
0
+
0
+ should "not allow #{attribute} to be less than #{min_length} chars long" do
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send("#{attribute}=", min_value)
0
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
0
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\""
0
+ assert_match(/short/, object.errors.on(attribute), "Error set on #{attribute} doesn't include \"short\" when set to \"#{min_value}\"")
0
+ end
0
+
0
+ should "not allow #{attribute} to be more than #{max_length} chars long" do
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send("#{attribute}=", max_value)
0
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\""
0
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\""
0
+ assert_match(/long/, object.errors.on(attribute), "Error set on #{attribute} doesn't include \"long\" when set to \"#{max_value}\"")
0
+ end
0
+ end
0
+
0
+ # Ensure that the attribute is in the range specified
0
+ # Requires an existing record
0
+ def should_ensure_value_in_range(attribute, range)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ min = range.first
0
+ max = range.last
0
+
0
+ should "not allow #{attribute} to be less than #{min}" do
0
+ v = min - 1
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send("#{attribute}=", v)
0
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
0
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
0
+ end
0
+
0
+ should "not allow #{attribute} to be more than #{max}" do
0
+ v = max + 1
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send("#{attribute}=", v)
0
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
0
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
0
+ end
0
+ end
0
+
0
+ # Ensure that the attribute is numeric
0
+ # Requires an existing record
0
+ def should_only_allow_numeric_values_for(*attributes)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ attributes.each do |attribute|
0
+ attribute = attribute.to_sym
0
+ should "only allow numeric values for #{attribute}" do
0
+ assert object = klass.find(:first), "Can't find first #{klass}"
0
+ object.send(:"#{attribute}=", "abcd")
0
+ assert !object.valid?, "Instance is still valid"
0
+ assert object.errors.on(attribute), "No errors found"
0
+ assert object.errors.on(attribute).to_a.include?('is not a number'), "Error message doesn't match"
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the has_many relationship exists.
0
+ # The last parameter may be a hash of options. Currently, the only supported option
0
+ # is :through
0
+ def should_have_many(*associations)
0
+ opts = associations.last.is_a?(Hash) ? associations.pop : {}
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ associations.each do |association|
0
+ should "have many #{association}#{" through #{opts[:through]}" if opts[:through]}" do
0
+ reflection = klass.reflect_on_association(association)
0
+ assert reflection
0
+ assert_equal :has_many, reflection.macro
0
+ assert_equal(opts[:through], reflection.options[:through]) if opts[:through]
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensures that the has_and_belongs_to_many relationship exists.
0
+ def should_have_and_belong_to_many(*associations)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ associations.each do |association|
0
+ should "should have and belong to many #{association}" do
0
+ assert klass.reflect_on_association(association)
0
+ assert_equal :has_and_belongs_to_many, klass.reflect_on_association(association).macro
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensure that the has_one relationship exists.
0
+ def should_have_one(*associations)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ associations.each do |association|
0
+ should "have one #{association}" do
0
+ assert klass.reflect_on_association(association)
0
+ assert_equal :has_one, klass.reflect_on_association(association).macro
0
+ end
0
+ end
0
+ end
0
+
0
+ # Ensure that the belongs_to relationship exists.
0
+ def should_belong_to(*associations)
0
+ klass = self.name.gsub(/Test$/, '').constantize
0
+ associations.each do |association|
0
+ should "belong_to #{association}" do
0
+ assert klass.reflect_on_association(association)
0
+ assert_equal :belongs_to, klass.reflect_on_association(association).macro
0
+ end
0
+ end
0
+ end
0
+ end
0
+end
0
\ No newline at end of file
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
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
0
@@ -0,0 +1,60 @@
0
+module TBTestHelpers
0
+ module Should
0
+ def Should.included(other)
0
+ @@_context_names = []
0
+ @@_setup_blocks = []
0
+ @@_teardown_blocks = []
0
+ end
0
+
0
+ def context(name, &context_block)
0
+ @@_context_names << name
0
+ context_block.bind(self).call
0
+ @@_context_names.pop
0
+ @@_setup_blocks.pop
0
+ @@_teardown_blocks.pop
0
+ end
0
+
0
+ def setup(&setup_block)
0
+ @@_setup_blocks << setup_block
0
+ end
0
+
0
+ def teardown(&teardown_block)
0
+ @@_teardown_blocks << teardown_block
0
+ end
0
+
0
+ # Defines a specification. Can be called either inside our outside of a context.
0
+ #
0
+ #
0
+ def should(name, opts = {}, &should_block)
0
+ unless @@_context_names.empty?
0
+ test_name = "test #{@@_context_names.join(" ")} should #{name}"
0
+ else
0
+ test_name = "test should #{name}"
0
+ end
0
+ test_name_sym = test_name.to_sym
0
+
0
+ raise ArgumentError, "'#{test_name}' is already defined" and return if self.instance_methods.include? test_name
0
+
0
+ setup_block = @@_setup_blocks.last
0
+ teardown_block = @@_teardown_blocks.last
0
+
0
+ if opts[:unimplemented]
0
+ define_method test_name_sym 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_sym do |*args|
0
+ setup_block.bind(self).call if setup_block
0
+ should_block.bind(self).call(*args)
0
+ teardown_block.bind(self).call if teardown_block
0
+ end
0
+ end
0
+ end
0
+
0
+ def should_eventually(name, &block)
0
+ should("eventually #{name}", {:unimplemented => true}, &block)
0
+ end
0
+ end
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
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
0
@@ -0,0 +1,48 @@
0
+require 'active_record_helpers'
0
+require 'should'
0
+
0
+class Test::Unit::TestCase
0
+ class << self
0
+ include TBTestHelpers::Should
0
+
0
+ # Loads all fixture files
0
+ def load_all_fixtures
0
+ all_fixtures = Dir.glob(File.join(RAILS_ROOT, "test", "fixtures", "*.yml")).collect do |f|
0
+ File.basename(f, '.yml').to_sym
0
+ end
0
+ fixtures *all_fixtures
0
+ end
0
+
0
+ end
0
+
0
+ # Ensures that the number of items in the collection changes
0
+ def assert_difference(object, method, difference, reload = false)
0
+ initial_value = object.send(method)
0
+ yield
0
+ reload and object.send(:reload)
0
+ assert_equal initial_value + difference, object.send(method), "#{object}##{method} after block"
0
+ end
0
+
0
+ # Ensures that object.method does not change
0
+ def assert_no_difference(object, method, reload = false, &block)
0
+ assert_difference(object, method, 0, reload, &block)
0
+ end
0
+
0
+ def report!(msg = "")
0
+ @controller.logger.info("TESTING: #{caller.first}: #{msg}")
0
+ end
0
+
0
+ # asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
0
+ def assert_same_elements(a1, a2)
0
+ [:select, :inject, :size].each do |m|
0
+ [a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a} is an array?") }
0
+ end
0
+
0
+ assert a1h = a1.inject({}){|h,e| h[e] = a1.select{|i| i == e}.size; h}
0
+ assert a2h = a2.inject({}){|h,e| h[e] = a2.select{|i| i == e}.size; h}
0
+
0
+ assert_equal(a1, a2)
0
+ end
0
+
0
+end
0
+
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
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
0
@@ -0,0 +1,63 @@
0
+require File.join(File.dirname(__FILE__), 'test_helper')
0
+
0
+class ContextTest < Test::Unit::TestCase
0
+
0
+ context "context with setup block" do
0
+ setup do
0
+ @blah = "blah"
0
+ end
0
+
0
+ should "have @blah == 'blah'" do
0
+ assert_equal "blah", @blah
0
+ end
0
+
0
+ should "have name set right" do
0
+ assert_match(/^test context with setup block/, self.to_s)
0
+ end
0
+ end
0
+
0
+ context "another context with setup block" do
0
+ setup do
0
+ @blah = "foo"
0
+ end
0
+
0
+ should "have @blah == 'foo'" do
0
+ assert_equal "foo", @blah
0
+ end
0
+
0
+ should "have name set right" do
0
+ assert_match(/^test another context with setup block/, self.to_s)
0
+ end
0
+ end
0
+
0
+ context "context with method definition" do
0
+ setup do
0
+ def hello; "hi"; end
0
+ end
0
+
0
+ should "be able to read that method" do
0
+ assert_equal "hi", hello
0
+ end
0
+
0
+ should "have name set right" do
0
+ assert_match(/^test context with method definition/, self.to_s)
0
+ end
0
+ end
0
+
0
+ context "final context" do
0
+ should "not define @blah" do
0
+ assert_nil @blah
0
+ end
0
+
0
+ context "with subcontext" do
0
+ should "be named correctly" do
0
+ assert_match(/^test final context with subcontext should be named correctly/, self.to_s)
0
+ end
0
+ end
0
+ end
0
+
0
+ should_eventually "pass anyway, since it's unimplemented" do
0
+ flunk "what?"
0
+ end
0
+
0
+end
...
 
 
 
 
 
 
...
1
2
3
4
5
6
0
@@ -0,0 +1,6 @@
0
+$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
0
+
0
+require 'rubygems'
0
+require 'active_support'
0
+require 'test/unit'
0
+require 'tb_test_helpers'

Comments

    No one has commented yet.