thoughtbot-shoulda 2.10.1 → 2.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) 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 +16 -10
  83. data/lib/shoulda/action_controller/helpers.rb +0 -47
@@ -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