shoulda 2.10.1 → 2.10.2

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 (85) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +4 -6
  2. data/README.rdoc +14 -12
  3. data/lib/shoulda.rb +1 -1
  4. data/lib/shoulda/action_controller.rb +0 -2
  5. data/lib/shoulda/action_controller/macros.rb +38 -75
  6. data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +4 -0
  7. data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +1 -1
  8. data/lib/shoulda/action_view/macros.rb +6 -1
  9. data/lib/shoulda/active_record/assertions.rb +4 -4
  10. data/lib/shoulda/active_record/helpers.rb +0 -13
  11. data/lib/shoulda/active_record/macros.rb +50 -127
  12. data/lib/shoulda/active_record/matchers.rb +2 -1
  13. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +1 -1
  14. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +5 -5
  15. data/lib/shoulda/active_record/matchers/association_matcher.rb +3 -3
  16. data/lib/shoulda/active_record/matchers/{have_index_matcher.rb → have_db_index_matcher.rb} +15 -8
  17. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +3 -0
  18. data/lib/shoulda/active_record/matchers/validate_format_of_matcher.rb +67 -0
  19. data/lib/shoulda/active_record/matchers/validation_matcher.rb +1 -0
  20. data/lib/shoulda/assertions.rb +18 -6
  21. data/lib/shoulda/context.rb +99 -1
  22. data/lib/shoulda/macros.rb +83 -23
  23. data/lib/shoulda/private_helpers.rb +1 -8
  24. data/lib/shoulda/test_unit.rb +3 -0
  25. data/test/fail_macros.rb +6 -1
  26. data/test/functional/posts_controller_test.rb +17 -21
  27. data/test/functional/users_controller_test.rb +1 -1
  28. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +1 -1
  29. data/test/matchers/active_record/allow_value_matcher_test.rb +24 -1
  30. data/test/matchers/active_record/association_matcher_test.rb +8 -3
  31. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +1 -1
  32. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +1 -1
  33. data/test/matchers/active_record/have_db_column_matcher_test.rb +1 -1
  34. data/test/matchers/active_record/{have_index_matcher_test.rb → have_db_index_matcher_test.rb} +24 -7
  35. data/test/matchers/active_record/have_named_scope_matcher_test.rb +1 -1
  36. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +1 -1
  37. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +1 -1
  38. data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
  39. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +1 -1
  40. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +1 -1
  41. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +1 -1
  42. data/test/matchers/controller/assign_to_matcher_test.rb +1 -1
  43. data/test/matchers/controller/filter_param_matcher_test.rb +1 -1
  44. data/test/matchers/controller/render_with_layout_matcher_test.rb +1 -1
  45. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +12 -7
  46. data/test/matchers/controller/respond_with_matcher_test.rb +1 -1
  47. data/test/matchers/controller/route_matcher_test.rb +1 -1
  48. data/test/matchers/controller/set_session_matcher_test.rb +9 -2
  49. data/test/matchers/controller/set_the_flash_matcher.rb +1 -1
  50. data/test/model_builder.rb +1 -1
  51. data/test/other/autoload_macro_test.rb +1 -1
  52. data/test/other/context_test.rb +45 -1
  53. data/test/other/convert_to_should_syntax_test.rb +3 -3
  54. data/test/other/helpers_test.rb +102 -3
  55. data/test/other/private_helpers_test.rb +6 -8
  56. data/test/other/should_test.rb +8 -3
  57. data/test/rails_root/app/controllers/{application.rb → application_controller.rb} +0 -0
  58. data/test/rails_root/app/controllers/posts_controller.rb +1 -0
  59. data/test/rails_root/app/models/pets/cat.rb +7 -0
  60. data/test/rails_root/app/models/profile.rb +2 -0
  61. data/test/rails_root/app/models/registration.rb +2 -0
  62. data/test/rails_root/app/models/user.rb +3 -0
  63. data/test/rails_root/config/boot.rb +6 -5
  64. data/test/rails_root/config/environment.rb +5 -1
  65. data/test/rails_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  66. data/test/rails_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  67. data/test/rails_root/db/migrate/20090513104502_create_cats.rb +12 -0
  68. data/test/rails_root/test/shoulda_macros/custom_macro.rb +1 -1
  69. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +1 -1
  70. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +1 -1
  71. data/test/rspec_test.rb +1 -1
  72. data/test/test_helper.rb +3 -10
  73. data/test/unit/address_test.rb +2 -2
  74. data/test/unit/cat_test.rb +7 -0
  75. data/test/unit/dog_test.rb +2 -3
  76. data/test/unit/flea_test.rb +1 -1
  77. data/test/unit/post_test.rb +2 -2
  78. data/test/unit/product_test.rb +2 -6
  79. data/test/unit/tag_test.rb +2 -2
  80. data/test/unit/tagging_test.rb +1 -1
  81. data/test/unit/user_test.rb +18 -8
  82. metadata +15 -9
  83. data/lib/shoulda/action_controller/helpers.rb +0 -47
  84. data/test/rails_root/log/sqlite3.log +0 -0
  85. data/test/rails_root/log/test.log +0 -0
@@ -4,12 +4,13 @@ require 'shoulda/active_record/matchers/allow_value_matcher'
4
4
  require 'shoulda/active_record/matchers/ensure_length_of_matcher'
5
5
  require 'shoulda/active_record/matchers/ensure_inclusion_of_matcher'
6
6
  require 'shoulda/active_record/matchers/validate_presence_of_matcher'
7
+ require 'shoulda/active_record/matchers/validate_format_of_matcher'
7
8
  require 'shoulda/active_record/matchers/validate_uniqueness_of_matcher'
8
9
  require 'shoulda/active_record/matchers/validate_acceptance_of_matcher'
9
10
  require 'shoulda/active_record/matchers/validate_numericality_of_matcher'
10
11
  require 'shoulda/active_record/matchers/association_matcher'
11
12
  require 'shoulda/active_record/matchers/have_db_column_matcher'
12
- require 'shoulda/active_record/matchers/have_index_matcher'
13
+ require 'shoulda/active_record/matchers/have_db_index_matcher'
13
14
  require 'shoulda/active_record/matchers/have_readonly_attribute_matcher'
14
15
  require 'shoulda/active_record/matchers/allow_mass_assignment_of_matcher'
15
16
  require 'shoulda/active_record/matchers/have_named_scope_matcher'
@@ -47,7 +47,7 @@ module Shoulda # :nodoc:
47
47
  attr_reader :failure_message, :negative_failure_message
48
48
 
49
49
  def description
50
- "protect #{@attribute} from mass updates"
50
+ "allow mass assignment of #{@attribute}"
51
51
  end
52
52
 
53
53
  private
@@ -6,8 +6,8 @@ module Shoulda # :nodoc:
6
6
  #
7
7
  # Options:
8
8
  # * <tt>with_message</tt> - value the test expects to find in
9
- # <tt>errors.on(:attribute)</tt>. Regexp or string. Defaults to the
10
- # translation for :invalid.
9
+ # <tt>errors.on(:attribute)</tt>. Regexp or string. If omitted,
10
+ # the test looks for any errors in <tt>errors.on(:attribute)</tt>.
11
11
  #
12
12
  # Example:
13
13
  # it { should_not allow_value('bad').for(:isbn) }
@@ -36,7 +36,6 @@ module Shoulda # :nodoc:
36
36
 
37
37
  def matches?(instance)
38
38
  @instance = instance
39
- @expected_message ||= :invalid
40
39
  if Symbol === @expected_message
41
40
  @expected_message = default_error_message(@expected_message)
42
41
  end
@@ -62,7 +61,7 @@ module Shoulda # :nodoc:
62
61
  @instance.valid?
63
62
  @errors = @instance.errors.on(@attribute)
64
63
  @errors = [@errors] unless @errors.is_a?(Array)
65
- errors_match_regexp? || errors_match_string?
64
+ @expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors != [nil])
66
65
  end
67
66
 
68
67
  def errors_match_regexp?
@@ -84,7 +83,8 @@ module Shoulda # :nodoc:
84
83
  end
85
84
 
86
85
  def expectation
87
- "errors to include #{@expected_message.inspect} " <<
86
+ "errors " <<
87
+ (@expected_message ? "to include #{@expected_message.inspect} " : "") <<
88
88
  "when #{@attribute} is set to #{@value.inspect}"
89
89
  end
90
90
 
@@ -133,7 +133,7 @@ module Shoulda # :nodoc:
133
133
 
134
134
  def through_association_exists?
135
135
  if through_reflection.nil?
136
- "#{model_class.name} does not have any relationship to #{@through}"
136
+ @missing = "#{model_class.name} does not have any relationship to #{@through}"
137
137
  false
138
138
  else
139
139
  true
@@ -142,10 +142,10 @@ module Shoulda # :nodoc:
142
142
 
143
143
  def through_association_correct?
144
144
  if @through == reflection.options[:through]
145
- "Expected #{model_class.name} to have #{@name} through #{@through}, " <<
146
- " but got it through #{reflection.options[:through]}"
147
145
  true
148
146
  else
147
+ @missing = "Expected #{model_class.name} to have #{@name} through #{@through}, " <<
148
+ "but got it through #{reflection.options[:through]}"
149
149
  false
150
150
  end
151
151
  end
@@ -14,15 +14,15 @@ module Shoulda # :nodoc:
14
14
  #
15
15
  # Examples:
16
16
  #
17
- # it { should have_index(:age) }
18
- # it { should have_index([:commentable_type, :commentable_id]) }
19
- # it { should have_index(:ssn).unique(true) }
17
+ # it { should have_db_index(:age) }
18
+ # it { should have_db_index([:commentable_type, :commentable_id]) }
19
+ # it { should have_db_index(:ssn).unique(true) }
20
20
  #
21
- def have_index(columns)
22
- HaveIndexMatcher.new(:have_index, columns)
21
+ def have_db_index(columns)
22
+ HaveDbIndexMatcher.new(:have_index, columns)
23
23
  end
24
24
 
25
- class HaveIndexMatcher # :nodoc:
25
+ class HaveDbIndexMatcher # :nodoc:
26
26
  def initialize(macro, columns)
27
27
  @macro = macro
28
28
  @columns = normalize_columns_to_array(columns)
@@ -47,7 +47,7 @@ module Shoulda # :nodoc:
47
47
  end
48
48
 
49
49
  def description
50
- "have a #{index_type} index on columns #{@columns}"
50
+ "have a #{index_type} index on columns #{@columns.join(' and ')}"
51
51
  end
52
52
 
53
53
  protected
@@ -88,7 +88,14 @@ module Shoulda # :nodoc:
88
88
  end
89
89
 
90
90
  def index_type
91
- @unique ? "unique" : "non-unique"
91
+ case @unique
92
+ when nil
93
+ ''
94
+ when false
95
+ 'non-unique'
96
+ else
97
+ 'unique'
98
+ end
92
99
  end
93
100
 
94
101
  def normalize_columns_to_array(columns)
@@ -2,6 +2,8 @@ module Shoulda # :nodoc:
2
2
  module ActiveRecord # :nodoc:
3
3
  module Matchers
4
4
 
5
+ # Deprecated.
6
+ #
5
7
  # Ensures that the model has a method named scope_call that returns a
6
8
  # NamedScope object with the proxy options set to the options you supply.
7
9
  # scope_call can be either a symbol, or a Ruby expression in a String
@@ -43,6 +45,7 @@ module Shoulda # :nodoc:
43
45
  # end
44
46
  #
45
47
  def have_named_scope(scope_call)
48
+ warn "[DEPRECATION] should_have_named_scope is deprecated."
46
49
  HaveNamedScopeMatcher.new(scope_call).in_context(self)
47
50
  end
48
51
 
@@ -0,0 +1,67 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the model is not valid if the given attribute is not
6
+ # formatted correctly.
7
+ #
8
+ # Options:
9
+ # * <tt>with_message</tt> - value the test expects to find in
10
+ # <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
11
+ # Defaults to the translation for <tt>:blank</tt>.
12
+ # * <tt>with(string to test against)</tt>
13
+ # * <tt>not_with(string to test against)</tt>
14
+ #
15
+ # Examples:
16
+ # it { should validate_format_of(:name).
17
+ # with('12345').
18
+ # with_message(/is not optional/) }
19
+ # it { should validate_format_of(:name).
20
+ # not_with('12D45').
21
+ # with_message(/is not optional/) }
22
+ #
23
+ def validate_format_of(attr)
24
+ ValidateFormatOfMatcher.new(attr)
25
+ end
26
+
27
+ class ValidateFormatOfMatcher < ValidationMatcher # :nodoc:
28
+
29
+ def initialize(attribute)
30
+ super
31
+ end
32
+
33
+ def with_message(message)
34
+ @expected_message = message if message
35
+ self
36
+ end
37
+
38
+ def with(value)
39
+ raise "You may not call both with and not_with" if @value_to_fail
40
+ @value_to_pass = value
41
+ self
42
+ end
43
+
44
+
45
+ def not_with(value)
46
+ raise "You may not call both with and not_with" if @value_to_pass
47
+ @value_to_fail = value
48
+ self
49
+ end
50
+
51
+
52
+ def matches?(subject)
53
+ super(subject)
54
+ @expected_message ||= :blank
55
+ return disallows_value_of(@value_to_fail, @expected_message) if @value_to_fail
56
+ allows_value_of(@value_to_pass, @expected_message) if @value_to_pass
57
+ end
58
+
59
+ def description
60
+ "#{@attribute} have a valid format"
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -18,6 +18,7 @@ module Shoulda # :nodoc:
18
18
  @subject = subject
19
19
  false
20
20
  end
21
+
21
22
 
22
23
  private
23
24
 
@@ -45,15 +45,27 @@ module Shoulda # :nodoc:
45
45
  end
46
46
 
47
47
  # Asserts that the given matcher returns true when +target+ is passed to #matches?
48
- def assert_accepts(matcher, target)
49
- success = matcher.matches?(target)
50
- assert_block(matcher.failure_message) { success }
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
51
57
  end
52
58
 
53
59
  # Asserts that the given matcher returns false when +target+ is passed to #matches?
54
- def assert_rejects(matcher, target)
55
- success = !matcher.matches?(target)
56
- assert_block(matcher.negative_failure_message) { success }
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
57
69
  end
58
70
  end
59
71
  end
@@ -59,7 +59,7 @@ module Shoulda
59
59
 
60
60
  def should(name, options = {}, &blk)
61
61
  if Shoulda.current_context
62
- block_given? ? Shoulda.current_context.should(name, options, &blk) : Should.current_context.should_eventually(name)
62
+ block_given? ? Shoulda.current_context.should(name, options, &blk) : Shoulda.current_context.should_eventually(name)
63
63
  else
64
64
  context_name = self.name.gsub(/Test/, "")
65
65
  context = Shoulda::Context.new(context_name, self) do
@@ -170,6 +170,98 @@ module Shoulda
170
170
  context.build
171
171
  end
172
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
+ if subject_block
234
+ instance_eval(&subject_block)
235
+ else
236
+ get_instance_of(self.class.described_type)
237
+ end
238
+ end
239
+
240
+ def subject_block # :nodoc:
241
+ (@shoulda_context && @shoulda_context.subject_block) || self.class.subject_block
242
+ end
243
+
244
+ def get_instance_of(object_or_klass) # :nodoc:
245
+ if object_or_klass.is_a?(Class)
246
+ klass = object_or_klass
247
+ ivar = "@#{instance_variable_name_for(klass)}"
248
+ if instance = instance_variable_get(ivar)
249
+ warn "[WARNING] Using #{ivar} as the subject. Future versions " <<
250
+ "of Shoulda will require an explicit subject using the " <<
251
+ "subject class method. Add this after your setup to avoid " <<
252
+ "this warning: subject { #{ivar} }"
253
+ instance
254
+ else
255
+ klass.new
256
+ end
257
+ else
258
+ object_or_klass
259
+ end
260
+ end
261
+
262
+ def instance_variable_name_for(klass) # :nodoc:
263
+ klass.to_s.split('::').last.underscore
264
+ end
173
265
  end
174
266
 
175
267
  class Context # :nodoc:
@@ -181,6 +273,7 @@ module Shoulda
181
273
  attr_accessor :teardown_blocks # blocks given via teardown methods
182
274
  attr_accessor :shoulds # array of hashes representing the should statements
183
275
  attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
276
+ attr_accessor :subject_block
184
277
 
185
278
  def initialize(name, parent, &blk)
186
279
  Shoulda.add_context(self)
@@ -224,6 +317,10 @@ module Shoulda
224
317
  self.should_eventuallys << { :name => name, :block => blk }
225
318
  end
226
319
 
320
+ def subject(&block)
321
+ self.subject_block = block
322
+ end
323
+
227
324
  def full_name
228
325
  parent_name = parent.full_name if am_subcontext?
229
326
  return [parent_name, name].join(" ").strip
@@ -246,6 +343,7 @@ module Shoulda
246
343
 
247
344
  context = self
248
345
  test_unit_class.send(:define_method, test_name) do
346
+ @shoulda_context = context
249
347
  begin
250
348
  context.run_parent_setup_blocks(self)
251
349
  should[:before].bind(self).call if should[:before]
@@ -3,70 +3,130 @@ require 'shoulda/private_helpers'
3
3
  module Shoulda # :nodoc:
4
4
  module Macros
5
5
  # Macro that creates a test asserting a change between the return value
6
- # of an expression that is run before and after the current setup block
6
+ # of a block that is run before and after the current setup block
7
7
  # is run. This is similar to Active Support's <tt>assert_difference</tt>
8
8
  # assertion, but supports more than just numeric values. See also
9
9
  # should_not_change.
10
10
  #
11
+ # The passed description will be used when generating the test name and failure messages.
12
+ #
11
13
  # Example:
12
14
  #
13
15
  # context "Creating a post" do
14
16
  # setup { Post.create }
15
- # should_change "Post.count", :by => 1
17
+ # should_change("the number of posts", :by => 1) { Post.count }
16
18
  # end
17
19
  #
18
20
  # As shown in this example, the <tt>:by</tt> option expects a numeric
19
21
  # difference between the before and after values of the expression. You
20
22
  # may also specify <tt>:from</tt> and <tt>:to</tt> options:
21
23
  #
22
- # should_change "Post.count", :from => 0, :to => 1
23
- # should_change "@post.title", :from => "old", :to => "new"
24
+ # should_change("the number of posts", :from => 0, :to => 1) { Post.count }
25
+ # should_change("the post title", :from => "old", :to => "new") { @post.title }
24
26
  #
25
27
  # Combinations of <tt>:by</tt>, <tt>:from</tt>, and <tt>:to</tt> are allowed:
26
28
  #
27
- # should_change "@post.title" # => assert the value changed in some way
28
- # should_change "@post.title", :from => "old" # => assert the value changed to anything other than "old"
29
- # should_change "@post.title", :to => "new" # => assert the value changed from anything other than "new"
30
- def should_change(expression, options = {})
29
+ # # Assert the value changed in some way:
30
+ # should_change("the post title") { @post.title }
31
+ #
32
+ # # Assert the value changed to anything other than "old:"
33
+ # should_change("the post title", :from => "old") { @post.title }
34
+ #
35
+ # # Assert the value changed to "new:"
36
+ # should_change("the post title", :to => "new") { @post.title }
37
+ def should_change(description, options = {}, &block)
31
38
  by, from, to = get_options!([options], :by, :from, :to)
32
- stmt = "change #{expression.inspect}"
39
+ stmt = "change #{description}"
33
40
  stmt << " from #{from.inspect}" if from
34
41
  stmt << " to #{to.inspect}" if to
35
42
  stmt << " by #{by.inspect}" if by
36
43
 
37
- expression_eval = lambda { eval(expression) }
38
- before = lambda { @_before_should_change = expression_eval.bind(self).call }
44
+ if block_given?
45
+ code = block
46
+ else
47
+ warn "[DEPRECATION] should_change(expression, options) is deprecated. " <<
48
+ "Use should_change(description, options) { code } instead."
49
+ code = lambda { eval(description) }
50
+ end
51
+ before = lambda { @_before_should_change = code.bind(self).call }
39
52
  should stmt, :before => before do
40
53
  old_value = @_before_should_change
41
- new_value = expression_eval.bind(self).call
42
- assert_operator from, :===, old_value, "#{expression.inspect} did not originally match #{from.inspect}" if from
43
- assert_not_equal old_value, new_value, "#{expression.inspect} did not change" unless by == 0
44
- assert_operator to, :===, new_value, "#{expression.inspect} was not changed to match #{to.inspect}" if to
54
+ new_value = code.bind(self).call
55
+ assert_operator from, :===, old_value, "#{description} did not originally match #{from.inspect}" if from
56
+ assert_not_equal old_value, new_value, "#{description} did not change" unless by == 0
57
+ assert_operator to, :===, new_value, "#{description} was not changed to match #{to.inspect}" if to
45
58
  assert_equal old_value + by, new_value if by
46
59
  end
47
60
  end
48
61
 
49
62
  # Macro that creates a test asserting no change between the return value
50
- # of an expression that is run before and after the current setup block
63
+ # of a block that is run before and after the current setup block
51
64
  # is run. This is the logical opposite of should_change.
52
65
  #
66
+ # The passed description will be used when generating the test name and failure message.
67
+ #
53
68
  # Example:
54
69
  #
55
70
  # context "Updating a post" do
56
71
  # setup { @post.update_attributes(:title => "new") }
57
- # should_not_change "Post.count"
72
+ # should_not_change("the number of posts") { Post.count }
58
73
  # end
59
- def should_not_change(expression)
60
- expression_eval = lambda { eval(expression) }
61
- before = lambda { @_before_should_not_change = expression_eval.bind(self).call }
62
- should "not change #{expression.inspect}", :before => before do
63
- new_value = expression_eval.bind(self).call
64
- assert_equal @_before_should_not_change, new_value, "#{expression.inspect} changed"
74
+ def should_not_change(description, &block)
75
+ if block_given?
76
+ code = block
77
+ else
78
+ warn "[DEPRECATION] should_not_change(expression) is deprecated. " <<
79
+ "Use should_not_change(description) { code } instead."
80
+ code = lambda { eval(description) }
81
+ end
82
+ before = lambda { @_before_should_not_change = code.bind(self).call }
83
+ should "not change #{description}", :before => before do
84
+ new_value = code.bind(self).call
85
+ assert_equal @_before_should_not_change, new_value, "#{description} changed"
65
86
  end
66
87
  end
67
88
 
89
+ # Macro that creates a test asserting that a record of the given class was
90
+ # created.
91
+ #
92
+ # Example:
93
+ #
94
+ # context "creating a post" do
95
+ # setup { Post.create(post_attributes) }
96
+ # should_create :post
97
+ # end
98
+ def should_create(class_name)
99
+ should_change_record_count_of(class_name, 1, 'create')
100
+ end
101
+
102
+ # Macro that creates a test asserting that a record of the given class was
103
+ # destroyed.
104
+ #
105
+ # Example:
106
+ #
107
+ # context "destroying a post" do
108
+ # setup { Post.first.destroy }
109
+ # should_destroy :post
110
+ # end
111
+ def should_destroy(class_name)
112
+ should_change_record_count_of(class_name, -1, 'destroy')
113
+ end
114
+
68
115
  private
69
116
 
117
+ def should_change_record_count_of(class_name, amount, action) # :nodoc:
118
+ klass = class_name.to_s.camelize.constantize
119
+ before = lambda do
120
+ @_before_change_record_count = klass.count
121
+ end
122
+ human_name = class_name.to_s.humanize.downcase
123
+ should "#{action} a #{human_name}", :before => before do
124
+ assert_equal @_before_change_record_count + amount,
125
+ klass.count,
126
+ "Expected to #{action} a #{human_name}"
127
+ end
128
+ end
129
+
70
130
  include Shoulda::Private
71
131
  end
72
132
  end