shoulda 2.0.6 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/README.rdoc +35 -7
  2. data/Rakefile +5 -3
  3. data/lib/shoulda.rb +7 -15
  4. data/lib/shoulda/action_mailer.rb +1 -1
  5. data/lib/shoulda/action_mailer/assertions.rb +32 -33
  6. data/lib/shoulda/active_record.rb +6 -2
  7. data/lib/shoulda/active_record/assertions.rb +62 -81
  8. data/lib/shoulda/active_record/helpers.rb +40 -0
  9. data/lib/shoulda/active_record/macros.rb +518 -639
  10. data/lib/shoulda/active_record/matchers.rb +42 -0
  11. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  12. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  13. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  14. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  15. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  16. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  17. data/lib/shoulda/active_record/matchers/have_index_matcher.rb +105 -0
  18. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +125 -0
  19. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  20. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  21. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  22. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  23. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  24. data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
  25. data/lib/shoulda/assertions.rb +50 -40
  26. data/lib/shoulda/autoload_macros.rb +46 -0
  27. data/lib/shoulda/context.rb +124 -126
  28. data/lib/shoulda/controller.rb +8 -8
  29. data/lib/shoulda/controller/formats/html.rb +158 -160
  30. data/lib/shoulda/controller/formats/xml.rb +132 -134
  31. data/lib/shoulda/controller/helpers.rb +51 -53
  32. data/lib/shoulda/controller/macros.rb +278 -258
  33. data/lib/shoulda/controller/resource_options.rb +211 -214
  34. data/lib/shoulda/helpers.rb +5 -7
  35. data/lib/shoulda/macros.rb +63 -64
  36. data/lib/shoulda/private_helpers.rb +16 -18
  37. data/lib/shoulda/rails.rb +1 -8
  38. data/lib/shoulda/rspec.rb +5 -0
  39. data/lib/shoulda/tasks/list_tests.rake +6 -1
  40. data/lib/shoulda/test_unit.rb +19 -0
  41. data/rails/init.rb +1 -1
  42. data/test/README +2 -2
  43. data/test/fail_macros.rb +16 -16
  44. data/test/functional/posts_controller_test.rb +5 -2
  45. data/test/matchers/allow_mass_assignment_of_matcher_test.rb +68 -0
  46. data/test/matchers/allow_value_matcher_test.rb +41 -0
  47. data/test/matchers/association_matcher_test.rb +258 -0
  48. data/test/matchers/ensure_inclusion_of_matcher_test.rb +80 -0
  49. data/test/matchers/ensure_length_of_matcher_test.rb +158 -0
  50. data/test/matchers/have_db_column_matcher_test.rb +169 -0
  51. data/test/matchers/have_index_matcher_test.rb +74 -0
  52. data/test/matchers/have_named_scope_matcher_test.rb +65 -0
  53. data/test/matchers/have_readonly_attributes_matcher_test.rb +29 -0
  54. data/test/matchers/validate_acceptance_of_matcher_test.rb +44 -0
  55. data/test/matchers/validate_numericality_of_matcher_test.rb +52 -0
  56. data/test/matchers/validate_presence_of_matcher_test.rb +86 -0
  57. data/test/matchers/validate_uniqueness_of_matcher_test.rb +141 -0
  58. data/test/model_builder.rb +61 -0
  59. data/test/other/autoload_macro_test.rb +18 -0
  60. data/test/other/helpers_test.rb +58 -0
  61. data/test/other/private_helpers_test.rb +1 -1
  62. data/test/other/should_test.rb +16 -16
  63. data/test/rails_root/app/controllers/posts_controller.rb +6 -5
  64. data/test/rails_root/app/models/pets/dog.rb +10 -0
  65. data/test/rails_root/app/models/treat.rb +3 -0
  66. data/test/rails_root/app/models/user.rb +2 -2
  67. data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
  68. data/test/rails_root/config/database.yml +1 -1
  69. data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  70. data/test/rails_root/db/migrate/001_create_users.rb +3 -2
  71. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  72. data/test/rails_root/log/test.log +0 -0
  73. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  74. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  75. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  76. data/test/test_helper.rb +3 -1
  77. data/test/unit/address_test.rb +1 -1
  78. data/test/unit/dog_test.rb +5 -2
  79. data/test/unit/post_test.rb +7 -3
  80. data/test/unit/product_test.rb +2 -2
  81. data/test/unit/tag_test.rb +2 -1
  82. data/test/unit/user_test.rb +17 -8
  83. metadata +44 -4
  84. data/test/rails_root/app/models/dog.rb +0 -5
@@ -1,74 +1,73 @@
1
1
  require 'shoulda/private_helpers'
2
2
 
3
- module ThoughtBot # :nodoc:
4
- module Shoulda # :nodoc:
5
- module Macros
6
- # Macro that creates a test asserting a change between the return value
7
- # of an expression that is run before and after the current setup block
8
- # is run. This is similar to Active Support's <tt>assert_difference</tt>
9
- # assertion, but supports more than just numeric values. See also
10
- # should_not_change.
11
- #
12
- # Example:
13
- #
14
- # context "Creating a post" do
15
- # setup { Post.create }
16
- # should_change "Post.count", :by => 1
17
- # end
18
- #
19
- # As shown in this example, the <tt>:by</tt> option expects a numeric
20
- # difference between the before and after values of the expression. You
21
- # may also specify <tt>:from</tt> and <tt>:to</tt> options:
22
- #
23
- # should_change "Post.count", :from => 0, :to => 1
24
- # should_change "@post.title", :from => "old", :to => "new"
25
- #
26
- # Combinations of <tt>:by</tt>, <tt>:from</tt>, and <tt>:to</tt> are allowed:
27
- #
28
- # should_change "@post.title" # => assert the value changed in some way
29
- # should_change "@post.title", :from => "old" # => assert the value changed to anything other than "old"
30
- # should_change "@post.title", :to => "new" # => assert the value changed from anything other than "new"
31
- def should_change(expression, options = {})
32
- by, from, to = get_options!([options], :by, :from, :to)
33
- stmt = "change #{expression.inspect}"
34
- stmt << " from #{from.inspect}" if from
35
- stmt << " to #{to.inspect}" if to
36
- stmt << " by #{by.inspect}" if by
3
+ module Shoulda # :nodoc:
4
+ module Macros
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
7
+ # is run. This is similar to Active Support's <tt>assert_difference</tt>
8
+ # assertion, but supports more than just numeric values. See also
9
+ # should_not_change.
10
+ #
11
+ # Example:
12
+ #
13
+ # context "Creating a post" do
14
+ # setup { Post.create }
15
+ # should_change "Post.count", :by => 1
16
+ # end
17
+ #
18
+ # As shown in this example, the <tt>:by</tt> option expects a numeric
19
+ # difference between the before and after values of the expression. You
20
+ # may also specify <tt>:from</tt> and <tt>:to</tt> options:
21
+ #
22
+ # should_change "Post.count", :from => 0, :to => 1
23
+ # should_change "@post.title", :from => "old", :to => "new"
24
+ #
25
+ # Combinations of <tt>:by</tt>, <tt>:from</tt>, and <tt>:to</tt> are allowed:
26
+ #
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 = {})
31
+ by, from, to = get_options!([options], :by, :from, :to)
32
+ stmt = "change #{expression.inspect}"
33
+ stmt << " from #{from.inspect}" if from
34
+ stmt << " to #{to.inspect}" if to
35
+ stmt << " by #{by.inspect}" if by
37
36
 
38
- expression_eval = lambda { eval(expression) }
39
- before = lambda { @_before_should_change = expression_eval.bind(self).call }
40
- should stmt, :before => before do
41
- old_value = @_before_should_change
42
- new_value = expression_eval.bind(self).call
43
- assert_operator from, :===, old_value, "#{expression.inspect} did not originally match #{from.inspect}" if from
44
- assert_not_equal old_value, new_value, "#{expression.inspect} did not change" unless by == 0
45
- assert_operator to, :===, new_value, "#{expression.inspect} was not changed to match #{to.inspect}" if to
46
- assert_equal old_value + by, new_value if by
47
- end
37
+ expression_eval = lambda { eval(expression) }
38
+ before = lambda { @_before_should_change = expression_eval.bind(self).call }
39
+ should stmt, :before => before do
40
+ 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
45
+ assert_equal old_value + by, new_value if by
48
46
  end
47
+ end
49
48
 
50
- # Macro that creates a test asserting no change between the return value
51
- # of an expression that is run before and after the current setup block
52
- # is run. This is the logical opposite of should_change.
53
- #
54
- # Example:
55
- #
56
- # context "Updating a post" do
57
- # setup { @post.update_attributes(:title => "new") }
58
- # should_not_change "Post.count"
59
- # end
60
- def should_not_change(expression)
61
- expression_eval = lambda { eval(expression) }
62
- before = lambda { @_before_should_not_change = expression_eval.bind(self).call }
63
- should "not change #{expression.inspect}", :before => before do
64
- new_value = expression_eval.bind(self).call
65
- assert_equal @_before_should_not_change, new_value, "#{expression.inspect} changed"
66
- end
49
+ # 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
51
+ # is run. This is the logical opposite of should_change.
52
+ #
53
+ # Example:
54
+ #
55
+ # context "Updating a post" do
56
+ # setup { @post.update_attributes(:title => "new") }
57
+ # should_not_change "Post.count"
58
+ # 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"
67
65
  end
66
+ end
68
67
 
69
- private
68
+ private
70
69
 
71
- include ThoughtBot::Shoulda::Private
72
- end
70
+ include Shoulda::Private
73
71
  end
74
72
  end
73
+
@@ -1,22 +1,20 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module Private # :nodoc:
4
- # Returns the values for the entries in the args hash who's keys are listed in the wanted array.
5
- # Will raise if there are keys in the args hash that aren't listed.
6
- def get_options!(args, *wanted)
7
- ret = []
8
- opts = (args.last.is_a?(Hash) ? args.pop : {})
9
- wanted.each {|w| ret << opts.delete(w)}
10
- raise ArgumentError, "Unsupported options given: #{opts.keys.join(', ')}" unless opts.keys.empty?
11
- return *ret
12
- end
1
+ module Shoulda # :nodoc:
2
+ module Private # :nodoc:
3
+ # Returns the values for the entries in the args hash who's keys are listed in the wanted array.
4
+ # Will raise if there are keys in the args hash that aren't listed.
5
+ def get_options!(args, *wanted)
6
+ ret = []
7
+ opts = (args.last.is_a?(Hash) ? args.pop : {})
8
+ wanted.each {|w| ret << opts.delete(w)}
9
+ raise ArgumentError, "Unsupported options given: #{opts.keys.join(', ')}" unless opts.keys.empty?
10
+ return *ret
11
+ end
13
12
 
14
- # Returns the model class constant, as determined by the test class name.
15
- #
16
- # class TestUser; model_class; end => User
17
- def model_class
18
- self.name.gsub(/Test$/, '').constantize
19
- end
13
+ # Returns the model class constant, as determined by the test class name.
14
+ #
15
+ # class TestUser; model_class; end => User
16
+ def model_class
17
+ self.name.gsub(/Test$/, '').constantize
20
18
  end
21
19
  end
22
20
  end
data/lib/shoulda/rails.rb CHANGED
@@ -8,12 +8,5 @@ require 'shoulda/action_mailer' if defined? ActionMailer::Base
8
8
 
9
9
  if defined?(RAILS_ROOT)
10
10
  # load in the 3rd party macros from vendorized plugins and gems
11
- Dir[File.join(RAILS_ROOT, "vendor", "{plugins,gems}", "*", "shoulda_macros", "*.rb")].each do |macro_file_path|
12
- require macro_file_path
13
- end
14
-
15
- # load in the local application specific macros
16
- Dir[File.join(RAILS_ROOT, "test", "shoulda_macros", "*.rb")].each do |macro_file_path|
17
- require macro_file_path
18
- end
11
+ Shoulda.autoload_macros RAILS_ROOT, File.join("vendor", "{plugins,gems}", "*")
19
12
  end
@@ -0,0 +1,5 @@
1
+ require 'shoulda/active_record/matchers'
2
+
3
+ Spec::Runner.configure do |config|
4
+ config.include Shoulda::ActiveRecord::Matchers, :type => :model
5
+ end
@@ -13,7 +13,12 @@ namespace :shoulda do
13
13
  test_files = Dir.glob(File.join('test', '**', '*_test.rb'))
14
14
  test_files.each do |file|
15
15
  load file
16
- klass = File.basename(file, '.rb').classify.constantize
16
+ klass = File.basename(file, '.rb').classify
17
+ unless Object.const_defined?(klass.to_s)
18
+ puts "Skipping #{klass} because it doesn't map to a Class"
19
+ next
20
+ end
21
+ klass = klass.constantize
17
22
 
18
23
  puts klass.name.gsub('Test', '')
19
24
 
@@ -0,0 +1,19 @@
1
+ require 'shoulda/context'
2
+ require 'shoulda/proc_extensions'
3
+ require 'shoulda/assertions'
4
+ require 'shoulda/macros'
5
+ require 'shoulda/helpers'
6
+ require 'shoulda/autoload_macros'
7
+ require 'shoulda/rails' if defined? RAILS_ROOT
8
+
9
+ module Test # :nodoc: all
10
+ module Unit
11
+ class TestCase
12
+ extend Shoulda::ClassMethods
13
+ include Shoulda::Assertions
14
+ extend Shoulda::Macros
15
+ include Shoulda::Helpers
16
+ end
17
+ end
18
+ end
19
+
data/rails/init.rb CHANGED
@@ -1 +1 @@
1
- require 'shoulda/rails'
1
+ require 'shoulda/rails' if RAILS_ENV == 'test'
data/test/README CHANGED
@@ -6,7 +6,7 @@ The test directory contains the following files and subdirectories:
6
6
 
7
7
  * rails_root - contains the stripped down rails application that the tests run against. The rails root contains:
8
8
  ** the models, controllers, and views defined under app/
9
- ** the sqlite3.rb environment file
9
+ ** the test.rb environment file
10
10
  ** a migration file for each model
11
11
  ** a shoulda initializer that simulates loading the plugin but without relying on vendor/plugins
12
12
  * fixtures - contain the sample DB data for each model
@@ -14,7 +14,7 @@ The test directory contains the following files and subdirectories:
14
14
  * unit - model tests for each of the models under rails_root/app
15
15
  * other - tests for the shoulda contexts, should statements, and assertions
16
16
  * test_helper.rb - responsible for initializing the test environment
17
- ** sets the rails_env to sqlite3
17
+ ** sets the rails_env to test
18
18
  ** sets the rails_root
19
19
  ** runs all the migrations against the in-memory sqlite3 db
20
20
  ** adds some magic to load the right fixture files
data/test/fail_macros.rb CHANGED
@@ -1,15 +1,15 @@
1
- module Thoughtbot
2
- module Shoulda
3
- class << self
4
- attr_accessor :expected_exceptions
5
- end
1
+ module Shoulda
2
+ class << self
3
+ attr_accessor :expected_exceptions
4
+ end
6
5
 
6
+ module ClassMethods
7
7
  # Enables the core shoulda test suite to test for failure scenarios. For
8
8
  # example, to ensure that a set of test macros should fail, do this:
9
9
  #
10
10
  # should_fail do
11
- # should_require_attributes :comments
12
- # should_protect_attributes :name
11
+ # should_validate_presence_of :comments
12
+ # should_not_allow_mass_assignment_of :name
13
13
  # end
14
14
  def should_fail(&block)
15
15
  context "should fail when trying to run:" do
@@ -18,17 +18,17 @@ module Thoughtbot
18
18
  Shoulda.expected_exceptions = nil
19
19
  end
20
20
  end
21
+ end
21
22
 
22
- class Context
23
- # alias_method_chain hack to allow the should_fail macro to work
24
- def should_with_failure_scenario(name, options = {}, &block)
25
- if Shoulda.expected_exceptions
26
- expected_exceptions = Shoulda.expected_exceptions
27
- failure_block = lambda { assert_raise(*expected_exceptions, &block.bind(self)) }
28
- end
29
- should_without_failure_scenario(name, options, &(failure_block || block))
23
+ class Context
24
+ # alias_method_chain hack to allow the should_fail macro to work
25
+ def should_with_failure_scenario(name, options = {}, &block)
26
+ if Shoulda.expected_exceptions
27
+ expected_exceptions = Shoulda.expected_exceptions
28
+ failure_block = lambda { assert_raise(*expected_exceptions, &block.bind(self)) }
30
29
  end
31
- alias_method_chain :should, :failure_scenario
30
+ should_without_failure_scenario(name, options, &(failure_block || block))
32
31
  end
32
+ alias_method_chain :should, :failure_scenario
33
33
  end
34
34
  end
@@ -13,7 +13,7 @@ class PostsControllerTest < Test::Unit::TestCase
13
13
  @response = ActionController::TestResponse.new
14
14
  @post = Post.find(:first)
15
15
  end
16
-
16
+
17
17
  # autodetects the :controller
18
18
  should_route :get, '/posts', :action => :index
19
19
  # explicitly specify :controller
@@ -24,7 +24,7 @@ class PostsControllerTest < Test::Unit::TestCase
24
24
  should_route :put, '/posts/1', :action => :update, :id => "1"
25
25
  should_route :delete, '/posts/1', :action => :destroy, :id => 1
26
26
  should_route :get, '/posts/new', :action => :new
27
-
27
+
28
28
  # Test the nested routes
29
29
  should_route :get, '/users/5/posts', :action => :index, :user_id => 5
30
30
  should_route :post, '/users/5/posts', :action => :create, :user_id => 5
@@ -73,6 +73,8 @@ class PostsControllerTest < Test::Unit::TestCase
73
73
  end
74
74
  should_assign_to :posts
75
75
  should_not_assign_to :foo, :bar
76
+ should_render_page_with_metadata :description => /Posts/, :title => /index/
77
+ should_render_page_with_metadata :keywords => "posts"
76
78
  end
77
79
 
78
80
  context "viewing posts for a user with rss format" do
@@ -94,6 +96,7 @@ class PostsControllerTest < Test::Unit::TestCase
94
96
  setup { get :show, :user_id => users(:first), :id => posts(:first) }
95
97
  should_render_with_layout 'wide'
96
98
  should_render_with_layout :wide
99
+ should_assign_to :false_flag
97
100
  end
98
101
 
99
102
  context "on GET to #new" do
@@ -0,0 +1,68 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class AllowMassAssignmentOfMatcherTest < Test::Unit::TestCase # :nodoc:
4
+
5
+ context "an attribute that is blacklisted from mass-assignment" do
6
+ setup do
7
+ define_model :example, :attr => :string do
8
+ attr_protected :attr
9
+ end
10
+ @model = Example.new
11
+ end
12
+
13
+ should "reject being mass-assignable" do
14
+ assert_rejects allow_mass_assignment_of(:attr), @model
15
+ end
16
+ end
17
+
18
+ context "an attribute that is not whitelisted for mass-assignment" do
19
+ setup do
20
+ define_model :example, :attr => :string, :other => :string do
21
+ attr_accessible :other
22
+ end
23
+ @model = Example.new
24
+ end
25
+
26
+ should "reject being mass-assignable" do
27
+ assert_rejects allow_mass_assignment_of(:attr), @model
28
+ end
29
+ end
30
+
31
+ context "an attribute that is whitelisted for mass-assignment" do
32
+ setup do
33
+ define_model :example, :attr => :string do
34
+ attr_accessible :attr
35
+ end
36
+ @model = Example.new
37
+ end
38
+
39
+ should "accept being mass-assignable" do
40
+ assert_accepts allow_mass_assignment_of(:attr), @model
41
+ end
42
+ end
43
+
44
+ context "an attribute not included in the mass-assignment blacklist" do
45
+ setup do
46
+ define_model :example, :attr => :string, :other => :string do
47
+ attr_protected :other
48
+ end
49
+ @model = Example.new
50
+ end
51
+
52
+ should "accept being mass-assignable" do
53
+ assert_accepts allow_mass_assignment_of(:attr), @model
54
+ end
55
+ end
56
+
57
+ context "an attribute on a class with no protected attributes" do
58
+ setup do
59
+ define_model :example, :attr => :string
60
+ @model = Example.new
61
+ end
62
+
63
+ should "accept being mass-assignable" do
64
+ assert_accepts allow_mass_assignment_of(:attr), @model
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class AllowValueMatcherTest < Test::Unit::TestCase # :nodoc:
4
+
5
+ context "an attribute with a format validation" do
6
+ setup do
7
+ define_model :example, :attr => :string do
8
+ validates_format_of :attr, :with => /abc/
9
+ end
10
+ @model = Example.new
11
+ end
12
+
13
+ should "allow a good value" do
14
+ assert_accepts allow_value("abcde").for(:attr), @model
15
+ end
16
+
17
+ should "not allow a bad value" do
18
+ assert_rejects allow_value("xyz").for(:attr), @model
19
+ end
20
+ end
21
+
22
+ context "an attribute with a format validation and a custom message" do
23
+ setup do
24
+ define_model :example, :attr => :string do
25
+ validates_format_of :attr, :with => /abc/, :message => 'bad value'
26
+ end
27
+ @model = Example.new
28
+ end
29
+
30
+ should "allow a good value" do
31
+ assert_accepts allow_value('abcde').for(:attr).with_message(/bad/),
32
+ @model
33
+ end
34
+
35
+ should "not allow a bad value" do
36
+ assert_rejects allow_value('xyz').for(:attr).with_message(/bad/),
37
+ @model
38
+ end
39
+ end
40
+
41
+ end