warp 1.0.1 → 1.1.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +35 -8
  5. data/Appraisals +67 -0
  6. data/Gemfile +4 -5
  7. data/README.md +57 -8
  8. data/Rakefile +4 -1
  9. data/gemfiles/rails_3.2_rspec_2.14.gemfile +22 -0
  10. data/gemfiles/rails_3.2_rspec_2.99.0.beta1.gemfile +22 -0
  11. data/gemfiles/rails_3.2_rspec_3.0.0.beta1.gemfile +23 -0
  12. data/gemfiles/rails_3.2_rspec_master.gemfile +23 -0
  13. data/gemfiles/rails_4.0_rspec_2.14.gemfile +22 -0
  14. data/gemfiles/rails_4.0_rspec_2.99.0.beta1.gemfile +22 -0
  15. data/gemfiles/rails_4.0_rspec_3.0.0.beta1.gemfile +23 -0
  16. data/gemfiles/rails_4.0_rspec_master.gemfile +23 -0
  17. data/gemfiles/rails_4.1.0.beta1_rspec_2.14.gemfile +23 -0
  18. data/gemfiles/rails_4.1.0.beta1_rspec_2.99.0.beta1.gemfile +23 -0
  19. data/gemfiles/rails_4.1.0.beta1_rspec_3.0.0.beta1.gemfile +24 -0
  20. data/gemfiles/rails_4.1.0.beta1_rspec_master.gemfile +24 -0
  21. data/lib/warp/controller_matchers/assign_matcher.rb +1 -3
  22. data/lib/warp/controller_matchers/set_flash_matcher.rb +19 -14
  23. data/lib/warp/matcher.rb +30 -0
  24. data/lib/warp/model_matchers/association_matcher.rb +56 -0
  25. data/lib/warp/model_matchers/attribute_matcher.rb +32 -0
  26. data/lib/warp/model_matchers/error_matcher.rb +12 -0
  27. data/lib/warp/model_matchers/matcher.rb +15 -0
  28. data/lib/warp/model_matchers/validation_matcher.rb +12 -0
  29. data/lib/warp/model_matchers.rb +10 -0
  30. data/lib/warp/version.rb +13 -1
  31. data/lib/warp.rb +4 -1
  32. data/spec/spec_helper.rb +15 -6
  33. data/spec/support/failure_message_helpers.rb +23 -0
  34. data/spec/support/match_helpers.rb +24 -12
  35. data/spec/support/model_helpers.rb +25 -0
  36. data/spec/support/with_contexts_helpers.rb +6 -0
  37. data/spec/warp/controller_matchers/assign_matcher_spec.rb +35 -33
  38. data/spec/warp/controller_matchers/set_flash_matcher_spec.rb +4 -12
  39. data/spec/warp/model_helpers/association_matcher_spec.rb +102 -0
  40. data/spec/warp/model_helpers/attribute_matcher_spec.rb +43 -0
  41. data/warp.gemspec +2 -4
  42. metadata +31 -33
  43. data/gemfiles/rails-3.2 +0 -17
  44. data/gemfiles/rails-4.0 +0 -17
  45. data/gemfiles/rails-4.1 +0 -18
@@ -0,0 +1,30 @@
1
+ module Warp
2
+ class Matcher
3
+ # Composable matchers are new in RSpec 3.
4
+ # Define basic helpers in their absence.
5
+ if defined?(RSpec::Matchers::Composable)
6
+ include RSpec::Matchers::Composable
7
+ else
8
+ def description_of(object)
9
+ object.inspect
10
+ end
11
+
12
+ def values_match?(expected, actual)
13
+ expected === actual || actual == expected
14
+ end
15
+ end
16
+
17
+ # RSpec 2 and 3 have different methods
18
+ # that they call on matcher to get the
19
+ # failure messages.
20
+ if RSpec::Version::STRING[0] == "2"
21
+ def failure_message_for_should
22
+ failure_message
23
+ end
24
+
25
+ def failure_message_for_should_not
26
+ failure_message_when_negated
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module Warp
2
+ module ModelMatchers
3
+ class AssociationMatcher < Warp::ModelMatchers::Matcher
4
+ attr_reader :macro, :key
5
+ attr_reader :failure_message, :failure_message_when_negated, :description
6
+
7
+ def initialize(macro, key)
8
+ @macro = macro
9
+ @key = key
10
+ end
11
+
12
+ def matches?(model_or_instance)
13
+ if association = model(model_or_instance).reflect_on_association(key)
14
+ actual_macro = association.macro
15
+ @failure_message = "expected to have association #{macro} :#{key}, but had #{actual_macro} :#{key}"
16
+ @failure_message_when_negated = "expected to not have association #{macro} :#{key}"
17
+ actual_macro == macro
18
+ else
19
+ @failure_message = "expected to have association #{macro} :#{key}"
20
+ @failure_message_when_negated = "expected to not have association #{macro} :#{key}"
21
+ false
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def model(model_or_instance)
28
+ if model_or_instance.is_a? Class
29
+ model_or_instance
30
+ else
31
+ model_or_instance.class
32
+ end
33
+ end
34
+ end
35
+
36
+ def have_many(key)
37
+ AssociationMatcher.new(:has_many, key)
38
+ end
39
+
40
+ def have_one(key)
41
+ AssociationMatcher.new(:has_one, key)
42
+ end
43
+
44
+ def belong_to(key)
45
+ AssociationMatcher.new(:belongs_to, key)
46
+ end
47
+
48
+ def have_and_belong_to_many(key)
49
+ if ActiveRecord::VERSION::STRING[0] == "4" && ActiveRecord::VERSION::STRING[3] != "0"
50
+ raise NotImplementedError, "In Rail 4.1+ the has_and_belongs_to_many helper produces a has_many :through association."
51
+ else
52
+ AssociationMatcher.new(:has_and_belongs_to_many, key)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ module Warp
2
+ module ModelMatchers
3
+ class AttributeMatcher < Warp::ModelMatchers::Matcher
4
+ attr_reader :attr_name
5
+ attr_reader :failure_message, :failure_message_when_negated, :description
6
+
7
+ def initialize(attr_name)
8
+ @attr_name = attr_name.to_sym
9
+ end
10
+
11
+ def matches?(model_or_instance)
12
+ if attributes(model_or_instance).any? {|actual| values_match?(attr_name, actual) }
13
+ @failure_message = "expected to have attribute #{description_of(attr_name)}"
14
+ @failure_message_when_negated = "expected to not have attribute #{description_of(attr_name)}"
15
+ true
16
+ else
17
+ @failure_message = "expected to have attribute #{description_of(attr_name)}"
18
+ @failure_message_when_negated = "expected to not have attribute #{description_of(attr_name)}"
19
+ false
20
+ end
21
+ end
22
+
23
+ def attributes(model_or_instance)
24
+ model(model_or_instance).column_names.map(&:to_sym)
25
+ end
26
+ end
27
+
28
+ def have_attribute(attr_name)
29
+ AttributeMatcher.new(attr_name)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module Warp
2
+ module ModelMatchers
3
+ class ErrorMatcher < Warp::Matcher
4
+
5
+
6
+ end
7
+
8
+ def have_error
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module Warp
2
+ module ModelMatchers
3
+ class Matcher < Warp::Matcher
4
+ private
5
+
6
+ def model(model_or_instance)
7
+ if model_or_instance.is_a? Class
8
+ model_or_instance
9
+ else
10
+ model_or_instance.class
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module Warp
2
+ module ModelMatchers
3
+ class ValidationMatcher < Warp::Matcher
4
+
5
+
6
+ end
7
+
8
+ def validate
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ require "warp/model_matchers/matcher"
2
+
3
+ require "warp/model_matchers/association_matcher"
4
+ require "warp/model_matchers/attribute_matcher"
5
+ # require "warp/model_matchers/error_matcher"
6
+ # require "warp/model_matchers/validation_matcher"
7
+
8
+ RSpec.configure do |config|
9
+ config.include Warp::ModelMatchers
10
+ end
data/lib/warp/version.rb CHANGED
@@ -1,3 +1,15 @@
1
1
  module Warp
2
- VERSION = "1.0.1"
2
+ module VERSION
3
+ MAJOR = 1
4
+ MINOR = 1
5
+ PATCH = 0
6
+
7
+ BETA = nil
8
+
9
+ def self.to_s
10
+ version_str = [MAJOR, MINOR, PATCH].map(&:to_s).join(".")
11
+ version_str << ".beta#{BETA}" if BETA
12
+ version_str
13
+ end
14
+ end
3
15
  end
data/lib/warp.rb CHANGED
@@ -2,4 +2,7 @@ require "warp/version"
2
2
 
3
3
  require "rspec"
4
4
 
5
- require "warp/controller_matchers"
5
+ require "warp/matcher"
6
+
7
+ require "warp/controller_matchers"
8
+ require "warp/model_matchers"
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,15 @@
1
- require 'bundler'
1
+ require "bundler"
2
+
3
+ require "simplecov"
4
+
5
+ if ENV["TRAVIS_CI"]
6
+ require "coveralls"
7
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
8
+ end
9
+
10
+ SimpleCov.start do
11
+ add_filter "/spec/"
12
+ end
2
13
 
3
14
  if ENV["TRAVIS_CI"]
4
15
  Bundler.require
@@ -6,16 +17,14 @@ else
6
17
  Bundler.require(:default, :tools)
7
18
  end
8
19
 
9
- require "active_support/all"
10
- require "action_controller"
11
-
12
- require "warp"
13
-
14
20
  Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f }
15
21
 
16
22
  RSpec.configure do |config|
17
23
  config.order = 'random'
18
24
 
19
25
  config.extend ControllerHelpers
26
+ config.extend FailureMessageHelpers
27
+ config.extend ModelHelpers
20
28
  config.extend WithContextsHelpers
29
+ config.include MatchHelpers
21
30
  end
@@ -0,0 +1,23 @@
1
+ module FailureMessageHelpers
2
+ if RSpec::Version::STRING[0] == "3"
3
+ FAILURE_MESSAGE_METHOD = :failure_message
4
+ FAILURE_MESSAGE_WHEN_NEGATED_METHOD = :failure_message_when_negated
5
+ else
6
+ FAILURE_MESSAGE_METHOD = :failure_message_for_should
7
+ FAILURE_MESSAGE_WHEN_NEGATED_METHOD = :failure_message_for_should_not
8
+ end
9
+
10
+ def describe_failure_message(&blk)
11
+ describe "##{FAILURE_MESSAGE_METHOD}" do
12
+ subject { super().send(FAILURE_MESSAGE_METHOD) }
13
+ instance_eval(&blk)
14
+ end
15
+ end
16
+
17
+ def describe_failure_message_when_negated(&blk)
18
+ describe "##{FAILURE_MESSAGE_WHEN_NEGATED_METHOD}" do
19
+ subject { super().send(FAILURE_MESSAGE_WHEN_NEGATED_METHOD) }
20
+ instance_eval(&blk)
21
+ end
22
+ end
23
+ end
@@ -1,18 +1,30 @@
1
- RSpec::Matchers.define :match do |value|
2
- match do |matcher|
3
- @matcher = matcher
4
- matcher.matches?(value)
5
- end
1
+ module MatchHelpers
2
+ class MatchMatcher
3
+ attr_reader :value, :matcher
6
4
 
7
- description do
8
- "match #{value}"
9
- end
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def matches?(matcher)
10
+ @matcher = matcher
11
+ matcher.matches?(value)
12
+ end
13
+
14
+ def description
15
+ "match #{value}"
16
+ end
17
+
18
+ def failure_message
19
+ "expect #{matcher} to match #{value} but did not"
20
+ end
10
21
 
11
- failure_message do
12
- "expect #{@matcher} to match #{value} but did not"
22
+ def failure_message_when_negated
23
+ "expect #{matcher} to not match #{value} but did"
24
+ end
13
25
  end
14
26
 
15
- failure_message_when_negated do
16
- "expect #{@matcher} to not match #{value} but did"
27
+ def match(value)
28
+ MatchMatcher.new(value)
17
29
  end
18
30
  end
@@ -0,0 +1,25 @@
1
+ module ModelHelpers
2
+ def build_model(&blk)
3
+ let(:model) do
4
+ Class.new(ActiveRecord::Base) do
5
+ def self.name
6
+ "TestModel"
7
+ end
8
+
9
+ def self.primary_key
10
+ "id"
11
+ end
12
+
13
+ def self.columns
14
+ @columns ||= []
15
+ end
16
+
17
+ def self.column(name, sql_type = nil, default = nil, null = true)
18
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
19
+ end
20
+
21
+ instance_eval(&blk)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,6 +2,8 @@ module WithContextsHelpers
2
2
  class WithContextsBuilder
3
3
  attr_accessor :contexts, :examples, :group
4
4
 
5
+ delegate :let, :before, :after, :subject, to: :group
6
+
5
7
  def initialize(group)
6
8
  @contexts = {}
7
9
  @group = group
@@ -11,6 +13,10 @@ module WithContextsHelpers
11
13
  self.contexts[name] = blk
12
14
  end
13
15
 
16
+ def describe(name, &blk)
17
+ self.contexts[name] = blk
18
+ end
19
+
14
20
  def behaviour(&blk)
15
21
  self.examples = blk
16
22
  end
@@ -35,9 +35,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
35
35
 
36
36
  specify { expect(subject).to match(_controller) }
37
37
 
38
- describe "#failure_message_when_negated" do
39
- subject { super().failure_message_when_negated }
40
-
38
+ describe_failure_message_when_negated do
41
39
  specify { expect(subject).to eq "expected @assign to not be assigned" }
42
40
  end
43
41
  end
@@ -49,9 +47,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
49
47
 
50
48
  specify { expect(subject).to_not match(_controller) }
51
49
 
52
- describe "#failure_message" do
53
- subject { super().failure_message }
54
-
50
+ describe_failure_message do
55
51
  specify { expect(subject).to eq "expected @assign to be assigned" }
56
52
  end
57
53
  end
@@ -75,9 +71,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
75
71
  context "with the right value" do
76
72
  specify { expect(subject).to match(_controller) }
77
73
 
78
- describe "#failure_message_when_negated" do
79
- subject { super().failure_message_when_negated }
80
-
74
+ describe_failure_message_when_negated do
81
75
  specify { expect(subject).to eq "expected @assign to not be assigned with #{expected_assign_value.inspect}" }
82
76
  end
83
77
  end
@@ -87,9 +81,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
87
81
 
88
82
  specify { expect(subject).to_not match(_controller) }
89
83
 
90
- describe "#failure_message" do
91
- subject { super().failure_message }
92
-
84
+ describe_failure_message do
93
85
  specify { expect(subject).to eq "expected @assign to be assigned with #{expected_assign_value.inspect} but was assigned with #{actual_assign_value.inspect}" }
94
86
  end
95
87
  end
@@ -114,9 +106,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
114
106
  context "with the right class" do
115
107
  specify { expect(subject).to match(_controller) }
116
108
 
117
- describe "#failure_message_when_negated" do
118
- subject { super().failure_message_when_negated }
119
-
109
+ describe_failure_message_when_negated do
120
110
  specify { expect(subject).to eq "expected @assign to not be assigned with an instance of #{expected_assign_class.name}" }
121
111
  end
122
112
  end
@@ -126,9 +116,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
126
116
 
127
117
  specify { expect(subject).to_not match(_controller) }
128
118
 
129
- describe "#failure_message" do
130
- subject { super().failure_message }
131
-
119
+ describe_failure_message do
132
120
  specify { expect(subject).to eq "expected @assign to be assigned with an instance of #{expected_assign_class.name} but was assigned with an instance of #{actual_assign_class.name}"
133
121
  }
134
122
  end
@@ -164,9 +152,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
164
152
  context "with the right class" do
165
153
  specify { expect(subject).to match(_controller) }
166
154
 
167
- describe "#failure_message_when_negated" do
168
- subject { super().failure_message_when_negated }
169
-
155
+ describe_failure_message_when_negated do
170
156
  specify { expect(subject).to eq "expected @assign to not be assigned with a new instance of #{expected_assign_class.name}" }
171
157
  end
172
158
  end
@@ -178,9 +164,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
178
164
 
179
165
  specify { expect(subject).to match(_controller) }
180
166
 
181
- describe "#failure_message_when_negated" do
182
- subject { super().failure_message_when_negated }
183
-
167
+ describe_failure_message_when_negated do
184
168
  specify { expect(subject).to eq "expected @assign to not be assigned with a new instance of #{expected_assign_class.name}" }
185
169
  end
186
170
  end
@@ -190,9 +174,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
190
174
 
191
175
  specify { expect(subject).to_not match(_controller) }
192
176
 
193
- describe "#failure_message" do
194
- subject { super().failure_message }
195
-
177
+ describe_failure_message do
196
178
  specify { expect(subject).to eq "expected @assign to be assigned with a new instance of #{expected_assign_class.name} but was assigned with a new instance of #{actual_assign_class.name}"
197
179
  }
198
180
  end
@@ -205,9 +187,7 @@ describe Warp::ControllerMatchers::AssignMatcher do
205
187
  context "with the right class" do
206
188
  specify { expect(subject).to_not match(_controller) }
207
189
 
208
- describe "#failure_message" do
209
- subject { super().failure_message }
210
-
190
+ describe_failure_message do
211
191
  specify { expect(subject).to eq "expected @assign to be assigned with a new instance of #{expected_assign_class.name} but was assigned with a persisted instance of #{actual_assign_class.name}"
212
192
  }
213
193
  end
@@ -218,15 +198,37 @@ describe Warp::ControllerMatchers::AssignMatcher do
218
198
 
219
199
  specify { expect(subject).to_not match(_controller) }
220
200
 
221
- describe "#failure_message" do
222
- subject { super().failure_message }
223
-
201
+ describe_failure_message do
224
202
  specify { expect(subject).to eq "expected @assign to be assigned with a new instance of #{expected_assign_class.name} but was assigned with a persisted instance of #{actual_assign_class.name}"
225
203
  }
226
204
  end
227
205
  end
228
206
  end
229
207
  end
208
+
209
+ context "multiple assertions" do
210
+ with_contexts do
211
+ context "with .with and .with_a" do
212
+ let(:matcher) { super().with(Object.new).with_a(Class.new) }
213
+ end
214
+
215
+ context "with .with_a and .with_a_new" do
216
+ let(:matcher) { super().with_a(Class.new).with_a_new(Class.new) }
217
+ end
218
+
219
+ context "with .with_a_new and .with" do
220
+ let(:matcher) { super().with_a_new(Class.new).with(Object.new) }
221
+ end
222
+
223
+ context "with .with, .with_a, and .with_a_new" do
224
+ let(:matcher) { super().with(Object.new).with_a(Class.new).with_a_new(Class.new) }
225
+ end
226
+
227
+ behaviour do
228
+ specify { expect{ subject }.to raise_error("Only one of .with, .with_a, and .with_a_new can be used with the assigns matcher.") }
229
+ end
230
+ end
231
+ end
230
232
  end
231
233
  end
232
234
  end
@@ -39,9 +39,7 @@ describe Warp::ControllerMatchers::SetFlashMatcher do
39
39
 
40
40
  specify { expect(subject).to match(_controller) }
41
41
 
42
- describe "#failure_message_when_negated" do
43
- subject { super().failure_message_when_negated }
44
-
42
+ describe_failure_message_when_negated do
45
43
  specify { expect(subject).to eq "expected flash[:#{flash_key}] to not be set" }
46
44
  end
47
45
  end
@@ -49,9 +47,7 @@ describe Warp::ControllerMatchers::SetFlashMatcher do
49
47
  context "with no flash set" do
50
48
  specify { expect(subject).to_not match(_controller) }
51
49
 
52
- describe "#failure_message" do
53
- subject { super().failure_message }
54
-
50
+ describe_failure_message do
55
51
  specify { expect(subject).to eq "expected flash[:#{flash_key}] to be set" }
56
52
  end
57
53
  end
@@ -76,9 +72,7 @@ describe Warp::ControllerMatchers::SetFlashMatcher do
76
72
 
77
73
  specify { expect(subject).to match(_controller) }
78
74
 
79
- describe "#failure_message_when_negated" do
80
- subject { super().failure_message_when_negated }
81
-
75
+ describe_failure_message_when_negated do
82
76
  specify { expect(subject).to eq "expected flash[:#{flash_key}] to not be set to #{expected_flash_value.inspect}" }
83
77
  end
84
78
  end
@@ -88,9 +82,7 @@ describe Warp::ControllerMatchers::SetFlashMatcher do
88
82
 
89
83
  specify { expect(subject).to_not match(_controller) }
90
84
 
91
- describe "#failure_message" do
92
- subject { super().failure_message }
93
-
85
+ describe_failure_message do
94
86
  specify { expect(subject).to eq "expected flash[:#{flash_key}] to be set to #{expected_flash_value.inspect}" }
95
87
  end
96
88
  end
@@ -0,0 +1,102 @@
1
+ require "spec_helper"
2
+
3
+ describe Warp::ModelMatchers::AssociationMatcher do
4
+ build_model do
5
+ belongs_to :foo
6
+ has_many :bars
7
+ has_one :baz
8
+ has_and_belongs_to_many :qux
9
+ end
10
+
11
+ with_contexts do
12
+ context "with model" do
13
+ let(:model_or_instance) { model }
14
+ end
15
+
16
+ context "with model instance" do
17
+ let(:model_or_instance) { model.new }
18
+ end
19
+
20
+ behaviour do
21
+ if ActiveRecord::VERSION::STRING[0] == "4" && ActiveRecord::VERSION::STRING[3] != "0"
22
+ specify { expect{ have_and_belong_to_many(:foo) }.to raise_error(NotImplementedError) }
23
+ end
24
+
25
+ with_contexts do
26
+ let(:no_association_key) { :foobar }
27
+
28
+ describe "#have_many" do
29
+ let(:matcher) { have_many(key) }
30
+
31
+ let(:matcher_macro) { :has_many }
32
+ let(:association_key) { :bars }
33
+ let(:wrong_association_key) { :foo }
34
+ end
35
+
36
+ describe "#have_one" do
37
+ let(:matcher) { have_one(key) }
38
+
39
+ let(:matcher_macro) { :has_one }
40
+ let(:association_key) { :baz }
41
+ let(:wrong_association_key) { :foo }
42
+ end
43
+
44
+ describe "#belong_to" do
45
+ let(:matcher) { belong_to(key) }
46
+
47
+ let(:matcher_macro) { :belongs_to }
48
+ let(:association_key) { :foo }
49
+ let(:wrong_association_key) { :bars }
50
+ end
51
+
52
+
53
+ unless ActiveRecord::VERSION::STRING[0] == "4" && ActiveRecord::VERSION::STRING[3] != "0"
54
+ describe "#have_and_belong_to_many" do
55
+ let(:matcher) { have_and_belong_to_many(key) }
56
+
57
+ let(:matcher_macro) { :has_and_belongs_to_many }
58
+ let(:association_key) { :qux }
59
+ let(:wrong_association_key) { :foo }
60
+ end
61
+ end
62
+
63
+ behaviour do
64
+ subject { matcher.tap {|m| m.matches?(model_or_instance) } }
65
+
66
+ context "when an association exists" do
67
+ context "and the association macro matches" do
68
+ let(:key) { association_key }
69
+
70
+ specify { expect(subject).to match(model_or_instance) }
71
+
72
+ describe_failure_message_when_negated do
73
+ specify { expect(subject).to eq "expected to not have association #{matcher_macro} :#{key}" }
74
+ end
75
+ end
76
+
77
+ context "and the association macro doesn't match" do
78
+ let(:key) { wrong_association_key }
79
+ let(:actual_macro) { model.reflect_on_association(key).macro }
80
+
81
+ specify { expect(subject).to_not match(model_or_instance) }
82
+
83
+ describe_failure_message do
84
+ specify { expect(subject).to eq "expected to have association #{matcher_macro} :#{key}, but had #{actual_macro} :#{key}" }
85
+ end
86
+ end
87
+ end
88
+
89
+ context "when an association doesn't exists" do
90
+ let(:key) { no_association_key }
91
+
92
+ specify { expect(subject).to_not match(model_or_instance) }
93
+
94
+ describe_failure_message do
95
+ specify { expect(subject).to eq "expected to have association #{matcher_macro} :#{key}" }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end