shoulda-matchers 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +11 -0
  2. data/.travis.yml +13 -0
  3. data/Appraisals +4 -6
  4. data/CONTRIBUTING.md +38 -0
  5. data/Gemfile +8 -5
  6. data/Gemfile.lock +77 -41
  7. data/NEWS.md +32 -0
  8. data/README.md +84 -0
  9. data/Rakefile +5 -36
  10. data/features/rails_integration.feature +88 -0
  11. data/features/step_definitions/rails_steps.rb +111 -0
  12. data/features/support/env.rb +5 -0
  13. data/gemfiles/3.0.gemfile +14 -0
  14. data/gemfiles/3.0.gemfile.lock +142 -0
  15. data/gemfiles/3.1.gemfile +16 -0
  16. data/gemfiles/3.1.gemfile.lock +164 -0
  17. data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +6 -9
  18. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +1 -3
  19. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +10 -6
  20. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +1 -4
  21. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +6 -6
  22. data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +11 -10
  23. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +0 -2
  24. data/lib/shoulda/matchers/action_controller/route_matcher.rb +17 -14
  25. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +24 -16
  26. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +52 -15
  27. data/lib/shoulda/matchers/action_mailer.rb +1 -1
  28. data/lib/shoulda/matchers/action_mailer/{have_sent_email.rb → have_sent_email_matcher.rb} +37 -21
  29. data/lib/shoulda/matchers/active_model.rb +1 -0
  30. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +9 -10
  31. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +60 -33
  32. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +0 -1
  33. data/lib/shoulda/matchers/active_model/helpers.rb +13 -9
  34. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +63 -0
  35. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +38 -8
  36. data/lib/shoulda/matchers/active_model/validation_matcher.rb +1 -5
  37. data/lib/shoulda/matchers/active_record.rb +3 -1
  38. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +127 -0
  39. data/lib/shoulda/matchers/active_record/association_matcher.rb +19 -7
  40. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +20 -5
  41. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +4 -10
  42. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +3 -7
  43. data/lib/shoulda/matchers/active_record/query_the_database_matcher.rb +107 -0
  44. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +111 -0
  45. data/lib/shoulda/matchers/integrations/rspec.rb +0 -1
  46. data/lib/shoulda/matchers/version.rb +1 -1
  47. data/shoulda-matchers.gemspec +30 -0
  48. data/spec/fixtures/addresses.yml +3 -0
  49. data/spec/fixtures/friendships.yml +0 -0
  50. data/spec/fixtures/posts.yml +5 -0
  51. data/spec/fixtures/products.yml +0 -0
  52. data/spec/fixtures/taggings.yml +0 -0
  53. data/spec/fixtures/tags.yml +9 -0
  54. data/spec/fixtures/users.yml +6 -0
  55. data/spec/shoulda/action_controller/assign_to_matcher_spec.rb +61 -0
  56. data/spec/shoulda/action_controller/filter_param_matcher_spec.rb +20 -0
  57. data/spec/shoulda/action_controller/redirect_to_matcher_spec.rb +40 -0
  58. data/spec/shoulda/action_controller/render_template_matcher_spec.rb +69 -0
  59. data/spec/shoulda/action_controller/render_with_layout_matcher_spec.rb +47 -0
  60. data/spec/shoulda/action_controller/respond_with_content_type_matcher_spec.rb +28 -0
  61. data/spec/shoulda/action_controller/respond_with_matcher_spec.rb +83 -0
  62. data/spec/shoulda/action_controller/route_matcher_spec.rb +65 -0
  63. data/spec/shoulda/action_controller/set_session_matcher_spec.rb +46 -0
  64. data/spec/shoulda/action_controller/set_the_flash_matcher_spec.rb +124 -0
  65. data/spec/shoulda/action_mailer/have_sent_email_spec.rb +293 -0
  66. data/spec/shoulda/active_model/allow_mass_assignment_of_matcher_spec.rb +95 -0
  67. data/spec/shoulda/active_model/allow_value_matcher_spec.rb +91 -0
  68. data/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb +57 -0
  69. data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +71 -0
  70. data/spec/shoulda/active_model/ensure_length_of_matcher_spec.rb +125 -0
  71. data/spec/shoulda/active_model/helpers_spec.rb +100 -0
  72. data/spec/shoulda/active_model/validate_acceptance_of_matcher_spec.rb +43 -0
  73. data/spec/shoulda/active_model/validate_confirmation_of_matcher_spec.rb +48 -0
  74. data/spec/shoulda/active_model/validate_format_of_matcher_spec.rb +38 -0
  75. data/spec/shoulda/active_model/validate_numericality_of_matcher_spec.rb +62 -0
  76. data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +121 -0
  77. data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +143 -0
  78. data/spec/shoulda/active_record/accept_nested_attributes_for_matcher_spec.rb +84 -0
  79. data/spec/shoulda/active_record/association_matcher_spec.rb +449 -0
  80. data/spec/shoulda/active_record/have_db_column_matcher_spec.rb +185 -0
  81. data/spec/shoulda/active_record/have_db_index_matcher_spec.rb +88 -0
  82. data/spec/shoulda/active_record/have_readonly_attributes_matcher_spec.rb +46 -0
  83. data/spec/shoulda/active_record/query_the_database_matcher_spec.rb +45 -0
  84. data/spec/shoulda/active_record/serialize_matcher_spec.rb +81 -0
  85. data/spec/spec_helper.rb +31 -0
  86. data/spec/support/model_builder.rb +149 -0
  87. metadata +211 -60
  88. data/CONTRIBUTION_GUIDELINES.rdoc +0 -10
  89. data/README.rdoc +0 -80
@@ -0,0 +1,107 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+
5
+ # Ensures that the number of database queries is known. Rails 3.1 or greater is required.
6
+ #
7
+ # Options:
8
+ # * <tt>when_calling</tt> - Required, the name of the method to examine.
9
+ # * <tt>with</tt> - Used in conjunction with <tt>when_calling</tt> to pass parameters to the method to examine.
10
+ # * <tt>or_less</tt> - Pass if the database is queried no more than the number of times specified, as opposed to exactly that number of times.
11
+ #
12
+ # Examples:
13
+ # it { should query_the_database(4.times).when_calling(:complicated_counting_method)
14
+ # it { should query_the_database(4.times).or_less.when_calling(:generate_big_report)
15
+ # it { should_not query_the_database.when_calling(:cached_count)
16
+ #
17
+ def query_the_database(times = nil)
18
+ QueryTheDatabaseMatcher.new(times)
19
+ end
20
+
21
+ class QueryTheDatabaseMatcher # :nodoc:
22
+ def initialize(times)
23
+ if times.respond_to?(:count)
24
+ @expected_query_count = times.count
25
+ else
26
+ @expected_query_count = times
27
+ end
28
+ end
29
+
30
+ def when_calling(method_name)
31
+ @method_name = method_name
32
+ self
33
+ end
34
+
35
+ def with(*method_arguments)
36
+ @method_arguments = method_arguments
37
+ self
38
+ end
39
+
40
+ def or_less
41
+ @expected_count_is_maximum = true
42
+ self
43
+ end
44
+
45
+ def matches?(subject)
46
+ @queries = []
47
+
48
+ subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |name, started, finished, id, payload|
49
+ @queries << payload unless filter_query(payload)
50
+ end
51
+
52
+ if @method_arguments
53
+ subject.send(@method_name, *@method_arguments)
54
+ else
55
+ subject.send(@method_name)
56
+ end
57
+
58
+ ActiveSupport::Notifications.unsubscribe(subscriber)
59
+
60
+ if @expected_count_is_maximum
61
+ @queries.length <= @expected_query_count
62
+ elsif @expected_query_count.present?
63
+ @queries.length == @expected_query_count
64
+ else
65
+ @queries.length > 0
66
+ end
67
+ end
68
+
69
+ def failure_message
70
+ if @expected_query_count
71
+ "Expected ##{@method_name} to cause #{@expected_query_count} database queries but it actually caused #{@queries.length} queries:" + friendly_queries
72
+ else
73
+ "Expected ##{@method_name} to query the database but it actually caused #{@queries.length} queries:" + friendly_queries
74
+ end
75
+ end
76
+
77
+ def negative_failure_message
78
+ if @expected_query_count
79
+ "Expected ##{@method_name} to not cause #{@expected_query_count} database queries but it actually caused #{@queries.length} queries:" + friendly_queries
80
+ else
81
+ "Expected ##{@method_name} to not query the database but it actually caused #{@queries.length} queries:" + friendly_queries
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def friendly_queries
88
+ @queries.map do |query|
89
+ "\n (#{query[:name]}) #{query[:sql]}"
90
+ end.join
91
+ end
92
+
93
+ def filter_query(query)
94
+ query[:name] == 'SCHEMA' || looks_like_schema(query[:sql])
95
+ end
96
+
97
+ def schema_terms
98
+ ['FROM sqlite_master', 'PRAGMA', 'SHOW TABLES', 'SHOW KEYS FROM', 'SHOW FIELDS FROM']
99
+ end
100
+
101
+ def looks_like_schema(sql)
102
+ schema_terms.any? { |term| sql.include?(term) }
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,111 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveRecord # :nodoc:
4
+
5
+ # Ensure that the field becomes serialized.
6
+ #
7
+ # Options:
8
+ # * <tt>:as</tt> - tests that the serialized attribute makes use of the class_name option.
9
+ #
10
+ # Example:
11
+ # it { should serialize(:details) }
12
+ # it { should serialize(:details).as(Hash) }
13
+ #
14
+ def serialize(name)
15
+ SerializeMatcher.new(name)
16
+ end
17
+
18
+ class SerializeMatcher # :nodoc:
19
+ def initialize(name)
20
+ @name = name.to_s
21
+ end
22
+
23
+ def as(type)
24
+ @type = type
25
+ self
26
+ end
27
+
28
+ def as_instance_of(type)
29
+ @instance_type = type
30
+ self
31
+ end
32
+
33
+ def matches?(subject)
34
+ @subject = subject
35
+ serialization_valid? && type_valid?
36
+ end
37
+
38
+ def failure_message
39
+ "Expected #{expectation} (#{@missing})"
40
+ end
41
+
42
+ def negative_failure_message
43
+ "Did not expect #{expectation}"
44
+ end
45
+
46
+ def description
47
+ description = "serialize :#{@name}"
48
+ description += " class_name => #{@type}" if @type
49
+ description
50
+ end
51
+
52
+ protected
53
+
54
+ def serialization_valid?
55
+ if model_class.serialized_attributes.keys.include?(@name)
56
+ true
57
+ else
58
+ @missing = "no serialized attribute called :#{@name}"
59
+ false
60
+ end
61
+ end
62
+
63
+ def class_valid?
64
+ if @type
65
+ klass = model_class.serialized_attributes[@name]
66
+ if klass == @type
67
+ true
68
+ else
69
+ if klass.respond_to?(:object_class) && klass.object_class == @type
70
+ true
71
+ else
72
+ @missing = ":#{@name} should be a type of #{@type}"
73
+ false
74
+ end
75
+ end
76
+ else
77
+ true
78
+ end
79
+ end
80
+
81
+ def model_class
82
+ @subject.class
83
+ end
84
+
85
+ def instance_class_valid?
86
+ if @instance_type
87
+ if model_class.serialized_attributes[@name].class == @instance_type
88
+ true
89
+ else
90
+ @missing = ":#{@name} should be an instance of #{@type}"
91
+ false
92
+ end
93
+ else
94
+ true
95
+ end
96
+ end
97
+
98
+ def type_valid?
99
+ class_valid? && instance_class_valid?
100
+ end
101
+
102
+ def expectation
103
+ expectation = "#{model_class.name} to serialize the attribute called :#{@name}"
104
+ expectation += " with a type of #{@type}" if @type
105
+ expectation += " with an instance of #{@instance_type}" if @instance_type
106
+ expectation
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -35,4 +35,3 @@ if defined?(::ActionMailer)
35
35
  end
36
36
  end
37
37
  end
38
-
@@ -1,5 +1,5 @@
1
1
  module Shoulda
2
2
  module Matchers
3
- VERSION = "1.0.0".dup
3
+ VERSION = '1.1.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
2
+ require 'shoulda/matchers/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "shoulda-matchers"
6
+ s.version = Shoulda::Matchers::VERSION.dup
7
+ s.authors = ["Tammer Saleh", "Joe Ferris", "Ryan McGeary", "Dan Croak",
8
+ "Matt Jankowski", "Stafford Brunk"]
9
+ s.date = Time.now.strftime("%Y-%m-%d")
10
+ s.email = "support@thoughtbot.com"
11
+ s.homepage = "http://thoughtbot.com/community/"
12
+ s.summary = "Making tests easy on the fingers and eyes"
13
+ s.description = "Making tests easy on the fingers and eyes"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency('activesupport', '>= 3.0.0')
21
+
22
+ s.add_development_dependency('appraisal', '~> 0.4.0')
23
+ s.add_development_dependency('aruba')
24
+ s.add_development_dependency('bourne', '~> 1.1.2')
25
+ s.add_development_dependency('bundler', '~> 1.1.0')
26
+ s.add_development_dependency('cucumber', '~> 1.1.9')
27
+ s.add_development_dependency('rails', '~> 3.0')
28
+ s.add_development_dependency('rake', '~> 0.9.2')
29
+ s.add_development_dependency('rspec-rails', '~> 2.6.1')
30
+ end
@@ -0,0 +1,3 @@
1
+ first:
2
+ title: Home
3
+ addressable: first (User)
File without changes
@@ -0,0 +1,5 @@
1
+ first:
2
+ id: 1
3
+ title: My Cute Kitten!
4
+ body: This is totally a cute kitten
5
+ user_id: 1
File without changes
File without changes
@@ -0,0 +1,9 @@
1
+ first:
2
+ id: 1
3
+ name: Stuff
4
+ second:
5
+ id: 2
6
+ name: Rails
7
+ third:
8
+ id: 3
9
+ name: Nothing
@@ -0,0 +1,6 @@
1
+ first:
2
+ id: 1
3
+ name: Some dude
4
+ age: 2
5
+ email: none@none.com
6
+ ssn: 123456789
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoulda::Matchers::ActionController::AssignToMatcher do
4
+ it "should include the actual class in the failure message" do
5
+ define_constant(:WrongClass) do
6
+ def to_s; "wrong class" end
7
+ end
8
+
9
+ controller = build_response { @var = WrongClass.new }
10
+ matcher = Shoulda::Matchers::ActionController::AssignToMatcher.new(:var).with_kind_of(Fixnum)
11
+ matcher.matches?(controller)
12
+
13
+ matcher.failure_message.should =~ /but got wrong class \(WrongClass\)$/
14
+ end
15
+
16
+ context "a controller that assigns to an instance variable" do
17
+ let(:controller) { build_response { @var = 'value' } }
18
+
19
+ it "should accept assigning to that variable" do
20
+ controller.should assign_to(:var)
21
+ end
22
+
23
+ it "should accept assigning to that variable with the correct class" do
24
+ controller.should assign_to(:var).with_kind_of(String)
25
+ end
26
+
27
+ it "should reject assigning to that variable with another class" do
28
+ controller.should_not assign_to(:var).with_kind_of(Fixnum)
29
+ end
30
+
31
+ it "should accept assigning the correct value to that variable" do
32
+ controller.should assign_to(:var).with('value')
33
+ end
34
+
35
+ it "should reject assigning another value to that variable" do
36
+ controller.should_not assign_to(:var).with('other')
37
+ end
38
+
39
+ it "should reject assigning to another variable" do
40
+ controller.should_not assign_to(:other)
41
+ end
42
+
43
+ it "should accept assigning to the same value in the test context" do
44
+ expected = 'value'
45
+ controller.should assign_to(:var).in_context(self).with { expected }
46
+ end
47
+
48
+ it "should reject assigning to the another value in the test context" do
49
+ expected = 'other'
50
+ controller.should_not assign_to(:var).in_context(self).with { expected }
51
+ end
52
+ end
53
+
54
+ context "a controller that assigns a nil value to an instance variable" do
55
+ let(:controller) { build_response { @var = nil } }
56
+
57
+ it "should accept assigning to that variable" do
58
+ controller.should assign_to(:var)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoulda::Matchers::ActionController::FilterParamMatcher do
4
+ context "given parameter filters" do
5
+ before do
6
+ Rails.application.config.filter_parameters = [:secret]
7
+ end
8
+
9
+ it "should accept filtering that parameter" do
10
+ nil.should filter_param(:secret)
11
+ end
12
+
13
+ it "should reject filtering another parameter" do
14
+ matcher = filter_param(:other)
15
+ matcher.matches?(nil).should be_false
16
+ matcher.failure_message.should include("Expected other to be filtered")
17
+ matcher.failure_message.should =~ /secret/
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoulda::Matchers::ActionController::RedirectToMatcher do
4
+ context "a controller that redirects" do
5
+ let(:controller) do
6
+ build_response { redirect_to '/some/url' }
7
+ end
8
+
9
+ it "accepts redirecting to that url" do
10
+ controller.should redirect_to('/some/url')
11
+ end
12
+
13
+ it "rejects redirecting to a different url" do
14
+ controller.should_not redirect_to('/some/other/url')
15
+ end
16
+
17
+ it "accepts redirecting to that url in a block" do
18
+ controller.should redirect_to('somewhere') { '/some/url' }
19
+ end
20
+
21
+ it "rejects redirecting to a different url in a block" do
22
+ controller.should_not redirect_to('somewhere else') { '/some/other/url' }
23
+ end
24
+ end
25
+
26
+ context "a controller that doesn't redirect" do
27
+ let(:controller) do
28
+ build_response { render :text => 'hello' }
29
+ end
30
+
31
+ it "rejects redirecting to a url" do
32
+ controller.should_not redirect_to('/some/url')
33
+ end
34
+ end
35
+
36
+ it "provides the correct description when provided a block" do
37
+ matcher = redirect_to('somewhere else') { '/some/other/url' }
38
+ matcher.description.should == 'redirect to somewhere else'
39
+ end
40
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoulda::Matchers::ActionController::RenderTemplateMatcher do
4
+ include ActionController::TemplateAssertions
5
+
6
+ context "a controller that renders a template" do
7
+ let(:controller) { build_response(:action => 'show') { render } }
8
+
9
+ it "accepts rendering that template" do
10
+ controller.should render_template(:show)
11
+ end
12
+
13
+ it "rejects rendering a different template" do
14
+ controller.should_not render_template(:index)
15
+ end
16
+
17
+ it "accepts rendering that template in the given context" do
18
+ controller.should render_template(:show).in_context(self)
19
+ end
20
+
21
+ it "rejects rendering a different template in the given context" do
22
+ controller.should_not render_template(:index).in_context(self)
23
+ end
24
+ end
25
+
26
+ context "a controller that renders a partial" do
27
+ let(:controller) { build_response(:partial => '_customer') { render :partial => 'customer' } }
28
+
29
+ it "accepts rendering that partial" do
30
+ controller.should render_template(:partial => '_customer')
31
+ end
32
+
33
+ it "rejects rendering a different template" do
34
+ controller.should_not render_template(:partial => '_client')
35
+ end
36
+
37
+ it "accepts rendering that template in the given context" do
38
+ controller.should render_template(:partial => '_customer').in_context(self)
39
+ end
40
+
41
+ it "rejects rendering a different template in the given context" do
42
+ controller.should_not render_template(:partial => '_client').in_context(self)
43
+ end
44
+ end
45
+
46
+ context "a controller that doesn't render partials" do
47
+ let(:controller) { build_response(:action => 'show') { render } }
48
+
49
+ it "should not render a partial" do
50
+ controller.should render_template(:partial => false)
51
+ end
52
+ end
53
+
54
+ context "a controller that renders a partial several times" do
55
+ let(:controller) { build_response(:partial => '_customer') { render :partial => 'customer', :collection => [1,2] } }
56
+
57
+ it "accepts rendering that partial twice" do
58
+ controller.should render_template(:partial => '_customer', :count => 2)
59
+ end
60
+ end
61
+
62
+ context "a controller that doesn't render a template" do
63
+ let(:controller) { build_response { render :nothing => true } }
64
+
65
+ it "rejects rendering a template" do
66
+ controller.should_not render_template(:show)
67
+ end
68
+ end
69
+ end