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.
- data/CHANGELOG.markdown +8 -0
- data/README.md +21 -49
- data/features/CHANGELOG.markdown +8 -0
- data/features/accessor_method_contracts.feature +107 -0
- data/features/customizing_arguments_and_return_values.feature +2 -2
- data/features/readme.md +21 -49
- data/features/rspec_mock_compatibility.feature +230 -0
- data/features/step_definitions/verified_double_steps.rb +17 -0
- data/features/{verified_double.feature → verified_mocks.feature} +5 -6
- data/features/verified_stubs.feature +94 -0
- data/lib/verified_double.rb +25 -10
- data/lib/verified_double/matchers.rb +35 -0
- data/lib/verified_double/method_signature_value.rb +15 -1
- data/lib/verified_double/method_signatures_report.rb +55 -0
- data/lib/verified_double/recording_double.rb +33 -12
- data/lib/verified_double/relays_to_internal_double_returning_self.rb +12 -0
- data/lib/verified_double/version.rb +1 -1
- data/spec/spec_helper.rb +3 -4
- data/spec/unit_helper.rb +3 -2
- data/spec/verified_double/matchers_spec.rb +96 -0
- data/spec/verified_double/method_signature_value_spec.rb +35 -0
- data/spec/verified_double/method_signatures_report_spec.rb +214 -0
- data/spec/verified_double/parse_method_signature_spec.rb +5 -1
- data/spec/verified_double/recording_double_spec.rb +103 -30
- data/spec/verified_double_spec.rb +70 -7
- data/verified_double.gemspec +0 -2
- metadata +17 -38
- data/lib/verified_double/get_registered_signatures.rb +0 -11
- data/lib/verified_double/get_unverified_signatures.rb +0 -13
- data/lib/verified_double/get_verified_signatures.rb +0 -17
- data/lib/verified_double/output_unverified_signatures.rb +0 -18
- data/lib/verified_double/report_unverified_signatures.rb +0 -16
- data/lib/verified_double/verify_doubles_service.rb +0 -15
- data/spec/verified_double/get_registered_signatures_spec.rb +0 -42
- data/spec/verified_double/get_unverified_signatures_spec.rb +0 -52
- data/spec/verified_double/get_verified_signatures_spec.rb +0 -63
- data/spec/verified_double/output_unverified_signatures_spec.rb +0 -58
- data/spec/verified_double/report_unverified_signatures_spec.rb +0 -57
- 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
|
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(
|
14
|
-
|
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
|
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.
|
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
|
data/lib/verified_double.rb
CHANGED
@@ -1,25 +1,26 @@
|
|
1
|
-
require 'rspec/
|
1
|
+
require 'rspec/mocks'
|
2
2
|
|
3
3
|
require 'verified_double/boolean'
|
4
|
-
require 'verified_double/
|
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/
|
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/
|
10
|
+
require 'verified_double/relays_to_internal_double_returning_self'
|
13
11
|
|
14
12
|
module VerifiedDouble
|
15
|
-
|
16
|
-
|
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(
|
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
|
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
|
-
|
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::
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
50
|
+
add_method_signature method
|
51
|
+
@double_call = double.should_receive(method)
|
52
|
+
self
|
53
|
+
end
|
45
54
|
|
46
|
-
|
47
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
20
|
+
config.after :suite do
|
21
|
+
VerifiedDouble.report_unverified_signatures(self)
|
22
|
+
end
|
24
23
|
end
|