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.
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