rspec-contracts 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce4b2a2faf48c3f9f368745eed2e0a33243fe872
4
- data.tar.gz: 8f01e23a2eed146f5bbe6407cd44fbaa51700cb8
3
+ metadata.gz: 87b15ff10f754beb89ee5b1d4243b8aea51ca5c3
4
+ data.tar.gz: b28190904c6dc5fd3c909ee73cdbb7c9dd4df3ac
5
5
  SHA512:
6
- metadata.gz: dbd6c1a8c64c9fcd1778ae4596abab1ed6f4d1e8d8015f376b6598ad6eb6ccf2670cb534e24c5149853886ca47620ac8f05964312f65f32e95fe5ed4d5dbf880
7
- data.tar.gz: bad52385149dc5c04ca685fe0551ab9414ec17db7dbbb54faa42330e3d0ccdf9043fe32ebc4e5bc16227abe722874712178e2effbf85d4ec380c83c62e7cca8d
6
+ metadata.gz: 27e3827e0404171952cdc793c8032caf74182e1621b2ad932c1d33cbf116ab63fd4ea8fcbbb4bde6bc8493f08c4ce4b9912341a02780b6d8e1eb2cb79cefe738
7
+ data.tar.gz: f3f091d6f3f01819b6122727b678992068ef2304f59e7036777ef4ce49200d43c9352ce106a9b95e12d5c8b0e71da657037da8ba921aece444f346db428246ec
data/History.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### Version 0.0.2
2
+ 2014-3-15
3
+
4
+ * Verify contracts by observing arguments and return values
5
+
1
6
  ### Version 0.0.1
2
7
  2014-3-4
3
8
 
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
- ## Conjure
1
+ ## rspec-contracts
2
+ [![Gem Version](https://badge.fury.io/rb/rspec-contracts.png)](http://badge.fury.io/rb/rspec-contracts)
3
+ [![Build Status](https://travis-ci.org/brianauton/rspec-contracts.png?branch=master)](https://travis-ci.org/brianauton/rspec-contracts)
4
+ [![Code Climate](https://codeclimate.com/github/brianauton/rspec-contracts.png)](https://codeclimate.com/github/brianauton/rspec-contracts)
2
5
 
3
- Automatic contract checking for your RSpec suite
6
+ Automatic contract verification for your RSpec suite
4
7
 
5
8
  ### Requirements
6
9
 
@@ -1,12 +1,14 @@
1
- require "rspec/core/example_group"
1
+ require "rspec/core"
2
+ require "rspec/contracts/interface"
2
3
  require "rspec/contracts/interface_proxy"
3
4
 
4
5
  module RSpec
5
6
  module Core
6
7
  class ExampleGroup
7
8
  def self.fulfill_contract(*interface_names)
8
- interface_names.each do |interface_name|
9
- Contracts::InterfaceProxy.create interface_name, described_class
9
+ interface_names.each do |name|
10
+ interface = Contracts::Interface.find_or_create name
11
+ Contracts::InterfaceProxy.create interface, described_class
10
12
  end
11
13
  end
12
14
  end
@@ -1,5 +1,6 @@
1
- require "rspec/mocks/test_double"
2
- require "rspec/contracts/proxy"
1
+ require "rspec/mocks"
2
+ require "rspec/contracts/interface"
3
+ require "rspec/contracts/mock_proxy"
3
4
 
4
5
  module RSpec
5
6
  module Contracts
@@ -7,12 +8,12 @@ module RSpec
7
8
  include Mocks::TestDouble
8
9
 
9
10
  def initialize(interface_name, *args)
10
- @interface_name = interface_name
11
- __initialize_as_test_double interface_name, *args
11
+ @interface = Interface.find_or_create interface_name
12
+ super
12
13
  end
13
14
 
14
15
  def __build_mock_proxy(order_group)
15
- Proxy.new self, order_group, @interface_name
16
+ MockProxy.new self, order_group, @interface
16
17
  end
17
18
  end
18
19
  end
@@ -1,23 +1,31 @@
1
- require "rspec/contracts/requirement"
2
- require "rspec/contracts/requirement_view"
1
+ require "rspec/contracts/interface_fulfillment"
3
2
 
4
3
  module RSpec
5
4
  module Contracts
6
5
  class Fulfillment
7
- def initialize(requirement_group)
8
- @requirement_group = requirement_group
6
+ attr_reader :interface_fulfillments
7
+
8
+ def initialize(interfaces, implementors)
9
+ @implementors = implementors
10
+ @interface_fulfillments = interfaces.map do |interface|
11
+ InterfaceFulfillment.new interface, implementors_for(interface)
12
+ end
9
13
  end
10
14
 
11
15
  def complete?
12
- false
16
+ incomplete_interfaces.empty?
17
+ end
18
+
19
+ def incomplete_interfaces
20
+ interface_fulfillments.reject(&:complete?)
13
21
  end
14
22
 
15
- def unfulfilled_requirements
16
- @requirement_group.requirements
23
+ def messages_count
24
+ interface_fulfillments.map(&:messages_count).inject(&:+) || 0
17
25
  end
18
26
 
19
- def requirements_count
20
- @requirement_group.requirements.count
27
+ def implementors_for(interface)
28
+ @implementors.select{ |i| i.interface_names.include? interface.name }
21
29
  end
22
30
  end
23
31
  end
@@ -1,3 +1,5 @@
1
+ require "rspec/contracts/message_view"
2
+
1
3
  module RSpec
2
4
  module Contracts
3
5
  class FulfillmentView
@@ -6,27 +8,25 @@ module RSpec
6
8
  end
7
9
 
8
10
  def render
9
- @fulfillment.complete? ? render_complete : render_incomplete
10
- end
11
-
12
- def render_complete
13
- "#{contracts_count} verified."
11
+ ([summary] + unfulfilled_views).join "\n"
14
12
  end
15
13
 
16
- def render_incomplete
17
- lines = unfulfilled_views
18
- lines.unshift("WARNING: #{unfulfilled_views.count} of #{contracts_count} unverified.")
19
- lines.join "\n"
14
+ def summary
15
+ unverified = unfulfilled_views.count
16
+ verified = @fulfillment.messages_count - unverified
17
+ "#{contracts_count}, #{verified} verified, #{unverified} unverified"
20
18
  end
21
19
 
22
20
  def unfulfilled_views
23
- @fulfillment.unfulfilled_requirements.map do |requirement|
24
- RSpec::Contracts::RequirementView.new(requirement).render
25
- end
21
+ @fulfillment.incomplete_interfaces.map do |fulfillment|
22
+ fulfillment.unfulfilled_messages.map do |message|
23
+ RSpec::Contracts::MessageView.new(fulfillment.interface.name, message).render
24
+ end
25
+ end.flatten
26
26
  end
27
27
 
28
28
  def contracts_count
29
- pluralize @fulfillment.requirements_count, "contract"
29
+ pluralize @fulfillment.messages_count, "contract"
30
30
  end
31
31
 
32
32
  def pluralize(number, noun)
@@ -0,0 +1,28 @@
1
+ module RSpec
2
+ module Contracts
3
+ class Implementor
4
+ attr_reader :interface_names, :messages
5
+
6
+ def initialize
7
+ @interface_names = []
8
+ @messages = []
9
+ end
10
+
11
+ def add_message(message)
12
+ messages << message
13
+ end
14
+
15
+ def self.collection
16
+ @collection ||= {}
17
+ end
18
+
19
+ def self.all
20
+ collection.values
21
+ end
22
+
23
+ def self.find_or_create(subject)
24
+ collection[subject] ||= new
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require "rspec/contracts/interface_group"
2
+
3
+ module RSpec
4
+ module Contracts
5
+ class Interface
6
+ attr_reader :name, :messages
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ @messages = []
11
+ end
12
+
13
+ def add_message(message)
14
+ @messages << message
15
+ end
16
+
17
+ def self.all
18
+ @all ||= InterfaceGroup.new
19
+ end
20
+
21
+ def self.find_or_create(name)
22
+ all.find_or_create name
23
+ end
24
+
25
+ def unique_messages
26
+ messages.uniq(&:to_hash)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ module RSpec
2
+ module Contracts
3
+ class InterfaceFulfillment
4
+ attr_reader :interface
5
+
6
+ def initialize(interface, implementors)
7
+ @interface = interface
8
+ @implementors = implementors
9
+ end
10
+
11
+ def complete?
12
+ unfulfilled_messages.empty?
13
+ end
14
+
15
+ def unfulfilled_messages
16
+ interface.unique_messages.reject{ |r| fulfilled_by_all? r }
17
+ end
18
+
19
+ def messages_count
20
+ interface.unique_messages.count
21
+ end
22
+
23
+ private
24
+
25
+ def fulfilled_by_all?(message)
26
+ @implementors.all?{ |i| fulfilled_by? message, i }
27
+ end
28
+
29
+ def fulfilled_by?(message, implementor)
30
+ implementor.messages.any? { |m| message.fully_described_by? m }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ require "rspec/contracts/interface"
2
+
3
+ module RSpec
4
+ module Contracts
5
+ class InterfaceGroup
6
+ include Enumerable
7
+
8
+ def initialize
9
+ @collection = {}
10
+ end
11
+
12
+ def find_or_create(name)
13
+ @collection[name] ||= Interface.new(name)
14
+ end
15
+
16
+ def each(&block)
17
+ @collection.values.each(&block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,11 +1,14 @@
1
+ require "rspec/contracts/implementor"
1
2
  require "rspec/contracts/method_proxy"
2
3
 
3
4
  module RSpec
4
5
  module Contracts
5
6
  class InterfaceProxy
6
- def initialize(interface_name, proxied_class)
7
+ def initialize(interface, proxied_class)
8
+ @implementor = Implementor.find_or_create proxied_class
9
+ @implementor.interface_names << interface.name
7
10
  proxied_class.instance_methods.select{|m| proxyable_method? m}.each do |method_name|
8
- MethodProxy.create interface_name, proxied_class, method_name
11
+ MethodProxy.create @implementor, proxied_class, method_name
9
12
  end
10
13
  end
11
14
 
@@ -0,0 +1,30 @@
1
+ module RSpec
2
+ module Contracts
3
+ class Message
4
+ attr_reader :method_name
5
+ attr_accessor :specifications
6
+
7
+ def initialize(method_name, specifications = {})
8
+ @method_name = method_name
9
+ @specifications = specifications
10
+ end
11
+
12
+ def fully_described_by?(message)
13
+ return false if message.method_name != method_name
14
+ @specifications.each do |name, specification|
15
+ unless specification.fully_described_by? message.specifications[name]
16
+ return false
17
+ end
18
+ end
19
+ true
20
+ end
21
+
22
+ def to_hash
23
+ {
24
+ :method_name => method_name,
25
+ :specifications => specifications.values.map(&:to_hash),
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module RSpec
2
+ module Contracts
3
+ class MessageArguments
4
+ attr_reader :arguments
5
+
6
+ def initialize(arguments)
7
+ @arguments = arguments
8
+ end
9
+
10
+ def fully_described_by?(other)
11
+ other && (arguments == other.arguments)
12
+ end
13
+
14
+ def to_hash
15
+ {:arguments => arguments}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module RSpec
2
+ module Contracts
3
+ class MessageReturn
4
+ attr_reader :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def fully_described_by?(other)
11
+ other && (value == other.value)
12
+ end
13
+
14
+ def to_hash
15
+ {:value => value}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module RSpec
2
+ module Contracts
3
+ class MessageView
4
+ def initialize(interface_name, message)
5
+ @interface_name = interface_name
6
+ @message = message
7
+ end
8
+
9
+ def render
10
+ arg_string = @message.specifications[:arguments] ? "()" : ""
11
+ return_string = @message.specifications[:return_value] ? "and return #{@message.specifications[:return_value].value.inspect}" : ""
12
+ "Interface '#{@interface_name}' must respond to '#{@message.method_name}#{arg_string}' #{return_string}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,6 @@
1
+ require "rspec/contracts/message"
2
+ require "rspec/contracts/message_arguments"
3
+
1
4
  module RSpec
2
5
  module Contracts
3
6
  class MethodProxy
@@ -5,10 +8,16 @@ module RSpec
5
8
  new(*args)
6
9
  end
7
10
 
11
+ def add_message(options)
12
+ message = Message.new(@method_name, options)
13
+ @implementor.add_message message
14
+ message
15
+ end
16
+
8
17
  private
9
18
 
10
- def initialize(interface_name, proxied_class, method_name)
11
- @interface_name = interface_name
19
+ def initialize(implementor, proxied_class, method_name)
20
+ @implementor = implementor
12
21
  @proxied_class = proxied_class
13
22
  @method_name = method_name
14
23
  install
@@ -16,8 +25,12 @@ module RSpec
16
25
 
17
26
  def install
18
27
  original_method = @proxied_class.instance_method @method_name
28
+ method_proxy = self
19
29
  @proxied_class.send :define_method, @method_name do |*args|
20
- original_method.bind(self).call(*args)
30
+ message = method_proxy.add_message :arguments => MessageArguments.new(args)
31
+ return_value = original_method.bind(self).call(*args)
32
+ message.specifications[:return_value] = MessageReturn.new(return_value)
33
+ return_value
21
34
  end
22
35
  end
23
36
  end
@@ -0,0 +1,51 @@
1
+ require "rspec/mocks"
2
+ require "rspec/contracts/message"
3
+ require "rspec/contracts/message_return"
4
+
5
+ module RSpec
6
+ module Contracts
7
+ class MockProxy < Mocks::Proxy
8
+ def initialize(object, order_group, interface)
9
+ super(object, order_group)
10
+ @method_doubles = Hash.new do |h, k|
11
+ h[k] = ContractMethodDouble.new(interface, object, k, self)
12
+ end
13
+ end
14
+ end
15
+
16
+ class ContractMethodDouble < Mocks::MethodDouble
17
+ attr_reader :message
18
+
19
+ def initialize(interface, object, method_name, proxy)
20
+ @message = Message.new method_name
21
+ interface.add_message @message
22
+ super(object, method_name, proxy)
23
+ end
24
+
25
+ def add_simple_stub(method_name, return_value)
26
+ @message.specifications[:return_value] = MessageReturn.new(return_value)
27
+ super
28
+ end
29
+
30
+ def message_expectation_class
31
+ ContractMessageExpectation
32
+ end
33
+ end
34
+
35
+ class ContractMessageExpectation < Mocks::MessageExpectation
36
+ def contract_message
37
+ @method_double.message
38
+ end
39
+
40
+ def with(*args)
41
+ contract_message.specifications[:arguments] = MessageArguments.new(args)
42
+ super
43
+ end
44
+
45
+ def and_return(*args)
46
+ contract_message.specifications[:return_value] = MessageReturn.new(args.first)
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,4 @@
1
+ require "rspec/mocks"
1
2
  require "rspec/contracts/double"
2
3
 
3
4
  module RSpec
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module Contracts
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -2,13 +2,15 @@ require "rspec/contracts/core_syntax"
2
2
  require "rspec/contracts/mocks_syntax"
3
3
  require "rspec/contracts/fulfillment"
4
4
  require "rspec/contracts/fulfillment_view"
5
- require "rspec/contracts/requirement"
5
+ require "rspec/contracts/implementor"
6
+ require "rspec/contracts/interface"
6
7
  require "rspec/core"
7
8
 
8
9
  RSpec.configure do |c|
9
10
  c.after(:suite) do
10
- requirement_group = RSpec::Contracts::Requirement.group
11
- fulfillment = RSpec::Contracts::Fulfillment.new requirement_group
11
+ interfaces = RSpec::Contracts::Interface.all
12
+ implementors = RSpec::Contracts::Implementor.all
13
+ fulfillment = RSpec::Contracts::Fulfillment.new interfaces, implementors
12
14
  print "\n" + RSpec::Contracts::FulfillmentView.new(fulfillment).render
13
15
  end
14
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-contracts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Auton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-05 00:00:00.000000000 Z
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -28,28 +28,28 @@ dependencies:
28
28
  name: guard-rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description:
@@ -67,13 +67,18 @@ files:
67
67
  - lib/rspec/contracts/double.rb
68
68
  - lib/rspec/contracts/fulfillment.rb
69
69
  - lib/rspec/contracts/fulfillment_view.rb
70
+ - lib/rspec/contracts/implementor.rb
71
+ - lib/rspec/contracts/interface.rb
72
+ - lib/rspec/contracts/interface_fulfillment.rb
73
+ - lib/rspec/contracts/interface_group.rb
70
74
  - lib/rspec/contracts/interface_proxy.rb
75
+ - lib/rspec/contracts/message.rb
76
+ - lib/rspec/contracts/message_arguments.rb
77
+ - lib/rspec/contracts/message_return.rb
78
+ - lib/rspec/contracts/message_view.rb
71
79
  - lib/rspec/contracts/method_proxy.rb
80
+ - lib/rspec/contracts/mock_proxy.rb
72
81
  - lib/rspec/contracts/mocks_syntax.rb
73
- - lib/rspec/contracts/proxy.rb
74
- - lib/rspec/contracts/requirement.rb
75
- - lib/rspec/contracts/requirement_group.rb
76
- - lib/rspec/contracts/requirement_view.rb
77
82
  - lib/rspec/contracts/version.rb
78
83
  homepage: http://github.com/brianauton/rspec-contracts
79
84
  licenses:
@@ -1,32 +0,0 @@
1
- require "rspec/mocks/proxy"
2
- require "rspec/contracts/requirement"
3
-
4
- module RSpec
5
- module Contracts
6
- class Proxy < Mocks::Proxy
7
- def initialize(object, order_group, interface_name)
8
- @interface_name = interface_name
9
- super(object, order_group)
10
- end
11
-
12
- def add_stub(location, method_name, opts={}, &implementation)
13
- create_requirement method_name
14
- super
15
- end
16
-
17
- def add_simple_stub(method_name, *args)
18
- create_requirement method_name, :return_value => args.first
19
- super
20
- end
21
-
22
- def add_message_expectation(location, method_name, opts={}, &block)
23
- create_requirement method_name
24
- super
25
- end
26
-
27
- def create_requirement(method_name, options = {})
28
- Requirement.create @interface_name, method_name, options
29
- end
30
- end
31
- end
32
- end
@@ -1,31 +0,0 @@
1
- require "rspec/contracts/requirement_group"
2
- require "rspec/contracts/requirement_view"
3
-
4
- module RSpec
5
- module Contracts
6
- class Requirement
7
- attr_reader :interface_name, :method_name, :arguments, :return_value
8
-
9
- def initialize(interface_name, method_name, options = {})
10
- @interface_name = interface_name
11
- @method_name = method_name
12
- @arguments = options[:arguments]
13
- @return_value = options[:return_value]
14
- end
15
-
16
- def self.group
17
- @group ||= RequirementGroup.new
18
- end
19
-
20
- def self.create(*args)
21
- group.add new(*args)
22
- end
23
-
24
- def matches?(requirement)
25
- [:interface_name, :method_name, :arguments, :return_value].select do |attribute|
26
- requirement.send(attribute) != send(attribute)
27
- end.empty?
28
- end
29
- end
30
- end
31
- end
@@ -1,19 +0,0 @@
1
- module RSpec
2
- module Contracts
3
- class RequirementGroup
4
- attr_accessor :requirements
5
-
6
- def initialize
7
- @requirements = []
8
- end
9
-
10
- def exists?(requirement)
11
- @requirements.any?{|r| r.matches? requirement}
12
- end
13
-
14
- def add(requirement)
15
- requirements << requirement unless exists? requirement
16
- end
17
- end
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- module RSpec
2
- module Contracts
3
- class RequirementView
4
- def initialize(requirement)
5
- @requirement = requirement
6
- end
7
-
8
- def render
9
- arg_string = @requirement.arguments ? "()" : ""
10
- return_string = @requirement.return_value ? "and return #{@requirement.return_value.inspect}" : ""
11
- "Interface '#{@requirement.interface_name}' must respond to '#{@requirement.method_name}#{arg_string}' #{return_string}"
12
- end
13
- end
14
- end
15
- end