verified_double 0.0.2 → 0.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 (39) hide show
  1. data/CHANGELOG.markdown +8 -0
  2. data/README.md +21 -49
  3. data/features/CHANGELOG.markdown +8 -0
  4. data/features/accessor_method_contracts.feature +107 -0
  5. data/features/customizing_arguments_and_return_values.feature +2 -2
  6. data/features/readme.md +21 -49
  7. data/features/rspec_mock_compatibility.feature +230 -0
  8. data/features/step_definitions/verified_double_steps.rb +17 -0
  9. data/features/{verified_double.feature → verified_mocks.feature} +5 -6
  10. data/features/verified_stubs.feature +94 -0
  11. data/lib/verified_double.rb +25 -10
  12. data/lib/verified_double/matchers.rb +35 -0
  13. data/lib/verified_double/method_signature_value.rb +15 -1
  14. data/lib/verified_double/method_signatures_report.rb +55 -0
  15. data/lib/verified_double/recording_double.rb +33 -12
  16. data/lib/verified_double/relays_to_internal_double_returning_self.rb +12 -0
  17. data/lib/verified_double/version.rb +1 -1
  18. data/spec/spec_helper.rb +3 -4
  19. data/spec/unit_helper.rb +3 -2
  20. data/spec/verified_double/matchers_spec.rb +96 -0
  21. data/spec/verified_double/method_signature_value_spec.rb +35 -0
  22. data/spec/verified_double/method_signatures_report_spec.rb +214 -0
  23. data/spec/verified_double/parse_method_signature_spec.rb +5 -1
  24. data/spec/verified_double/recording_double_spec.rb +103 -30
  25. data/spec/verified_double_spec.rb +70 -7
  26. data/verified_double.gemspec +0 -2
  27. metadata +17 -38
  28. data/lib/verified_double/get_registered_signatures.rb +0 -11
  29. data/lib/verified_double/get_unverified_signatures.rb +0 -13
  30. data/lib/verified_double/get_verified_signatures.rb +0 -17
  31. data/lib/verified_double/output_unverified_signatures.rb +0 -18
  32. data/lib/verified_double/report_unverified_signatures.rb +0 -16
  33. data/lib/verified_double/verify_doubles_service.rb +0 -15
  34. data/spec/verified_double/get_registered_signatures_spec.rb +0 -42
  35. data/spec/verified_double/get_unverified_signatures_spec.rb +0 -52
  36. data/spec/verified_double/get_verified_signatures_spec.rb +0 -63
  37. data/spec/verified_double/output_unverified_signatures_spec.rb +0 -58
  38. data/spec/verified_double/report_unverified_signatures_spec.rb +0 -57
  39. data/spec/verified_double/verify_doubles_service_spec.rb +0 -24
@@ -2,6 +2,10 @@ Given /^the following classes:$/ do |string|
2
2
  write_file 'lib/main.rb', string
3
3
  end
4
4
 
5
+ Given /^the test suite includes VerifiedDouble to verify doubles with accessor methods:$/ do |string|
6
+ write_file 'spec/spec_helper.rb', string
7
+ end
8
+
5
9
  Given /^the test suite has an after\(:suite\) callback asking VerifiedDouble to report unverified doubles:$/ do |string|
6
10
  write_file 'spec/spec_helper.rb', string
7
11
  end
@@ -10,6 +14,10 @@ Given /^a test that uses VerifiedDouble to mock an object:$/ do |string|
10
14
  write_file 'spec/main_spec.rb', string
11
15
  end
12
16
 
17
+ Given /^a test that uses VerifiedDouble to stub an object:$/ do |string|
18
+ write_file 'spec/main_spec.rb', string
19
+ end
20
+
13
21
  Given /^a test that uses VerifiedDouble to mock a class:$/ do |string|
14
22
  write_file 'spec/main_spec.rb', string
15
23
  end
@@ -18,6 +26,10 @@ Given /^the test suite has a contract test for the mock:$/ do |string|
18
26
  write_file 'spec/contract_test_for_main_spec.rb', string
19
27
  end
20
28
 
29
+ Given /^the test suite has a contract test for the stub:$/ do |string|
30
+ write_file 'spec/contract_test_for_main_spec.rb', string
31
+ end
32
+
21
33
  Given /^the test suite does not have a contract test for the mock$/ do
22
34
  # do nothing
23
35
  end
@@ -35,3 +47,8 @@ Then /^I should not see any output saying the mock is unverified$/ do
35
47
  assert_no_partial_output("The following mocks are not verified", all_output)
36
48
  assert_success('pass')
37
49
  end
50
+
51
+ Then /^I should not see any output saying the stub is unverified$/ do
52
+ assert_no_partial_output("The following mocks are not verified", all_output)
53
+ assert_success('pass')
54
+ end
@@ -1,4 +1,4 @@
1
- Feature: Verified double
1
+ Feature: 01. Verified mocks
2
2
  As a developer
3
3
  I want to be informed if the mocks I use are verified by contract tests
4
4
 
@@ -10,8 +10,8 @@ Feature: Verified double
10
10
  collaborator.some_method(input)
11
11
  end
12
12
 
13
- def self.do_something(collaborator, input)
14
- collaborator.some_method(input)
13
+ def self.do_something(input)
14
+ Collaborator.some_method(input)
15
15
  end
16
16
  end
17
17
 
@@ -32,13 +32,12 @@ Feature: Verified double
32
32
 
33
33
  And the test suite has an after(:suite) callback asking VerifiedDouble to report unverified doubles:
34
34
  """
35
- require 'pry'
36
35
  require 'verified_double'
37
36
  require 'main'
38
37
 
39
38
  RSpec.configure do |config|
40
39
  config.after :suite do
41
- VerifiedDouble::ReportUnverifiedSignatures.new(VerifiedDouble.registry, self).execute
40
+ VerifiedDouble.report_unverified_signatures(self)
42
41
  end
43
42
  end
44
43
  """
@@ -108,7 +107,7 @@ Feature: Verified double
108
107
 
109
108
  it "tests something" do
110
109
  class_double.should_receive(:some_method).with(input).and_return(output)
111
- ObjectUnderTest.new.do_something(class_double, input)
110
+ ObjectUnderTest.do_something(input)
112
111
  end
113
112
  end
114
113
  """
@@ -0,0 +1,94 @@
1
+ Feature: 02. Verified stubs
2
+ As a developer
3
+ I want to be informed if the stubs I use are verified by contract tests
4
+
5
+ Background:
6
+ Given the following classes:
7
+ """
8
+ class ObjectUnderTest
9
+ def do_something(collaborator, input)
10
+ collaborator.some_method(input)
11
+ end
12
+ end
13
+
14
+ class Collaborator
15
+ def some_method(input)
16
+ end
17
+ end
18
+
19
+ class SomeInput
20
+ end
21
+
22
+ class SomeOutput
23
+ end
24
+ """
25
+
26
+ And the test suite has an after(:suite) callback asking VerifiedDouble to report unverified doubles:
27
+ """
28
+ require 'verified_double'
29
+ require 'main'
30
+
31
+ RSpec.configure do |config|
32
+ config.after :suite do
33
+ VerifiedDouble.report_unverified_signatures(self)
34
+ end
35
+ end
36
+ """
37
+ Scenario: Stubbed doubles
38
+ Given a test that uses VerifiedDouble to stub an object:
39
+ """
40
+ require 'spec_helper'
41
+ describe ObjectUnderTest do
42
+ let(:input) { SomeInput.new }
43
+ let(:output) { SomeOutput.new }
44
+ let(:instance_double) { VerifiedDouble.of_instance('Collaborator') }
45
+
46
+ it "tests something" do
47
+ instance_double.stub(:some_method).with(input).and_return(output)
48
+ ObjectUnderTest.new.do_something(instance_double, input)
49
+ end
50
+ end
51
+ """
52
+
53
+ And the test suite has a contract test for the stub:
54
+ """
55
+ require 'spec_helper'
56
+
57
+ describe 'Collaborator' do
58
+ it "tests something", verifies_contract: 'Collaborator#some_method(SomeInput)=>SomeOutput' do
59
+ # do nothing
60
+ end
61
+ end
62
+ """
63
+
64
+ When I run the test suite
65
+ Then I should not see any output saying the stub is unverified
66
+
67
+ Scenario: Stubbed doubles using hash syntax
68
+ Given a test that uses VerifiedDouble to stub an object:
69
+ """
70
+ require 'spec_helper'
71
+ describe ObjectUnderTest do
72
+ let(:input) { SomeInput.new }
73
+ let(:output) { SomeOutput.new }
74
+ let(:instance_double) { VerifiedDouble.of_instance('Collaborator', some_method: output) }
75
+
76
+ it "tests something" do
77
+ ObjectUnderTest.new.do_something(instance_double, input)
78
+ end
79
+ end
80
+ """
81
+
82
+ And the test suite has a contract test for the stub:
83
+ """
84
+ require 'spec_helper'
85
+
86
+ describe 'Collaborator' do
87
+ it "tests something", verifies_contract: 'Collaborator#some_method=>SomeOutput' do
88
+ # do nothing
89
+ end
90
+ end
91
+ """
92
+
93
+ When I run the test suite
94
+ Then I should not see any output saying the stub is unverified
@@ -1,25 +1,26 @@
1
- require 'rspec/fire'
1
+ require 'rspec/mocks'
2
2
 
3
3
  require 'verified_double/boolean'
4
- require 'verified_double/get_registered_signatures'
5
- require 'verified_double/get_unverified_signatures'
6
- require 'verified_double/get_verified_signatures'
4
+ require 'verified_double/matchers'
7
5
  require 'verified_double/method_signature'
8
6
  require 'verified_double/method_signature_value'
9
- require 'verified_double/output_unverified_signatures'
7
+ require 'verified_double/method_signatures_report'
10
8
  require 'verified_double/parse_method_signature'
11
9
  require 'verified_double/recording_double'
12
- require 'verified_double/report_unverified_signatures'
10
+ require 'verified_double/relays_to_internal_double_returning_self'
13
11
 
14
12
  module VerifiedDouble
15
- def self.of_class(class_name)
16
- RecordingDouble.new(RSpec::Fire::FireClassDoubleBuilder.build(class_name).as_replaced_constant).tap do |double|
13
+ extend RSpec::Mocks::ExampleMethods
14
+
15
+ def self.of_class(class_name, method_stubs = {})
16
+ class_double = stub_const(class_name, Class.new, transfer_nested_constants: true)
17
+ RecordingDouble.new(class_double, class_name, method_stubs).tap do |double|
17
18
  registry << double
18
19
  end
19
20
  end
20
21
 
21
- def self.of_instance(class_name)
22
- RecordingDouble.new(RSpec::Fire::FireObjectDouble.new(class_name)).tap do |double|
22
+ def self.of_instance(class_name, method_stubs = {})
23
+ RecordingDouble.new(double(class_name), class_name, method_stubs).tap do |double|
23
24
  registry << double
24
25
  end
25
26
  end
@@ -27,5 +28,19 @@ module VerifiedDouble
27
28
  def self.registry
28
29
  @registry ||= []
29
30
  end
31
+
32
+ def self.report_unverified_signatures(nested_example_group)
33
+ MethodSignaturesReport.new
34
+ .set_registered_signatures
35
+ .set_verified_signatures_from_tags(nested_example_group)
36
+ .set_verified_signatures_from_matchers
37
+ .merge_verified_signatures
38
+ .identify_unverified_signatures
39
+ .output_unverified_signatures
40
+ end
41
+
42
+ def self.verified_signatures_from_matchers
43
+ @verified_signatures_from_matchers ||= []
44
+ end
30
45
  end
31
46
 
@@ -0,0 +1,35 @@
1
+ require 'rspec/expectations'
2
+
3
+ module VerifiedDouble
4
+ module Matchers
5
+ extend RSpec::Matchers::DSL
6
+
7
+ matcher :verify_accessor_contract do |expected|
8
+ match do |actual|
9
+ method_signature = ParseMethodSignature.new(expected).execute
10
+
11
+ VerifiedDouble.verified_signatures_from_matchers << method_signature
12
+
13
+ raise CannotHandleMultipleReturnValues if method_signature.return_values.size > 1
14
+
15
+ value = method_signature.return_values.first.as_instance
16
+ actual.send "#{method_signature.method}=", value
17
+ actual.send(method_signature.method) == value
18
+ end
19
+ end
20
+
21
+ matcher :verify_reader_contract do |expected|
22
+ match do |actual|
23
+ method_signature = ParseMethodSignature.new(expected).execute
24
+
25
+ VerifiedDouble.verified_signatures_from_matchers << method_signature
26
+
27
+ raise CannotHandleMultipleReturnValues if method_signature.return_values.size > 1
28
+
29
+ actual.send(method_signature.method).is_a?(method_signature.return_values.first.value)
30
+ end
31
+ end
32
+
33
+ class CannotHandleMultipleReturnValues < Exception; end
34
+ end
35
+ end
@@ -14,8 +14,22 @@ module VerifiedDouble
14
14
  end
15
15
  end
16
16
 
17
+ def as_instance
18
+ if self.value.is_a?(Class)
19
+ begin
20
+ value.new
21
+ rescue NoMethodError
22
+ Object.new
23
+ end
24
+ else
25
+ self.value
26
+ end
27
+ end
28
+
17
29
  def modified_class
18
- if value == true or value == false
30
+ if value.is_a?(VerifiedDouble::RecordingDouble)
31
+ value.class_name.constantize
32
+ elsif value == true or value == false
19
33
  VerifiedDouble::Boolean
20
34
  else
21
35
  value.class
@@ -0,0 +1,55 @@
1
+ module VerifiedDouble
2
+ class MethodSignaturesReport
3
+ attr_accessor :registered_signatures, :unverified_signatures, :verified_signatures,
4
+ :verified_signatures_from_tags,:verified_signatures_from_matchers
5
+
6
+ def initialize
7
+ @registered_signatures = []
8
+ @unverified_signatures = []
9
+ @verified_signatures = []
10
+ @verified_signatures_from_tags = []
11
+ @verified_signatures_from_matchers = []
12
+ end
13
+
14
+ def identify_unverified_signatures
15
+ @unverified_signatures = @registered_signatures.select{|registered_signature|
16
+ @verified_signatures.all?{|verified_signature|
17
+ ! registered_signature.accepts?(verified_signature) } }
18
+ self
19
+ end
20
+
21
+ def merge_verified_signatures
22
+ @verified_signatures = @verified_signatures_from_tags + @verified_signatures_from_matchers
23
+ self
24
+ end
25
+
26
+ def output_unverified_signatures
27
+ if @unverified_signatures.any?
28
+ output = ["The following mocks are not verified:" ] + @unverified_signatures.map(&:recommended_verified_signature).map(&:to_s).sort
29
+ puts output.join("\n")
30
+ end
31
+ self
32
+ end
33
+
34
+ def set_registered_signatures
35
+ @registered_signatures = VerifiedDouble.registry.map(&:method_signatures).flatten.uniq
36
+ self
37
+ end
38
+
39
+ def set_verified_signatures_from_matchers
40
+ @verified_signatures_from_matchers = VerifiedDouble.verified_signatures_from_matchers
41
+ self
42
+ end
43
+
44
+ def set_verified_signatures_from_tags(nested_example_group)
45
+ @verified_signatures_from_tags = nested_example_group
46
+ .class
47
+ .descendant_filtered_examples
48
+ .map{|example| example.metadata[:verifies_contract] }
49
+ .compact
50
+ .uniq
51
+ .map{|method_signature_string| ParseMethodSignature.new(method_signature_string).execute }
52
+ self
53
+ end
54
+ end
55
+ end
@@ -1,10 +1,23 @@
1
1
  require 'delegate'
2
+ require 'verified_double/relays_to_internal_double_returning_self'
2
3
 
3
4
  module VerifiedDouble
4
5
  class RecordingDouble < ::SimpleDelegator
5
- def initialize(double)
6
+ extend VerifiedDouble::RelaysToInternalDoubleReturningSelf
7
+
8
+ relays_to_internal_double_returning_self :any_number_of_times, :and_raise,
9
+ :and_throw, :at_least, :at_most, :exactly, :once, :twice
10
+
11
+ attr_reader :class_name
12
+
13
+ def initialize(double, class_name, method_stubs={})
6
14
  @double = double
15
+ @class_name = class_name
16
+
7
17
  super(@double)
18
+ method_stubs.each do |method, output|
19
+ self.stub(method).and_return(output)
20
+ end
8
21
  end
9
22
 
10
23
  def and_return(return_value)
@@ -14,11 +27,7 @@ module VerifiedDouble
14
27
  end
15
28
 
16
29
  def class_double?
17
- ! double.is_a?(RSpec::Fire::FireObjectDouble)
18
- end
19
-
20
- def class_name
21
- double.instance_variable_get('@name')
30
+ ! double.is_a?(RSpec::Mocks::Mock)
22
31
  end
23
32
 
24
33
  def double
@@ -38,13 +47,14 @@ module VerifiedDouble
38
47
  end
39
48
 
40
49
  def should_receive(method)
41
- method_signature = MethodSignature.new(
42
- class_name: class_name,
43
- method_operator: method_operator,
44
- method: method.to_s)
50
+ add_method_signature method
51
+ @double_call = double.should_receive(method)
52
+ self
53
+ end
45
54
 
46
- self.method_signatures << method_signature
47
- @double_call = super(method)
55
+ def stub(method)
56
+ add_method_signature method
57
+ @double_call = double.stub(method)
48
58
  self
49
59
  end
50
60
 
@@ -58,5 +68,16 @@ module VerifiedDouble
58
68
  @double_call.with(*args)
59
69
  self
60
70
  end
71
+
72
+ private
73
+
74
+ def add_method_signature(method)
75
+ method_signature = MethodSignature.new(
76
+ class_name: class_name,
77
+ method_operator: method_operator,
78
+ method: method.to_s)
79
+
80
+ self.method_signatures << method_signature
81
+ end
61
82
  end
62
83
  end
@@ -0,0 +1,12 @@
1
+ module VerifiedDouble
2
+ module RelaysToInternalDoubleReturningSelf
3
+ def relays_to_internal_double_returning_self(*methods)
4
+ methods.each do |method|
5
+ define_method method do |*args|
6
+ @double_call.send(method, *args)
7
+ self
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module VerifiedDouble
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -7,7 +7,6 @@ require 'verified_double'
7
7
  #
8
8
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
9
9
  RSpec.configure do |config|
10
- config.include(RSpec::Fire)
11
10
  config.treat_symbols_as_metadata_keys_with_true_values = true
12
11
  config.run_all_when_everything_filtered = true
13
12
  config.filter_run :focus
@@ -17,8 +16,8 @@ RSpec.configure do |config|
17
16
  # the seed, which is printed after each run.
18
17
  # --seed 1234
19
18
  config.order = 'random'
20
- end
21
19
 
22
- RSpec::Fire.configure do |config|
23
- config.verify_constant_names = true
20
+ config.after :suite do
21
+ VerifiedDouble.report_unverified_signatures(self)
22
+ end
24
23
  end