verified_double 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|