surrogate 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Readme.md CHANGED
@@ -1,4 +1,43 @@
1
- Explanation and examples coming soon.
1
+ Explanation and examples coming soon, but here is a simple example I wrote up for a lightning talk:
2
+
3
+ ```ruby
4
+ require 'surrogate'
5
+ require 'surrogate/rspec'
6
+
7
+ module Mock
8
+ class User
9
+ Surrogate.endow self
10
+ define(:name) { 'Josh' }
11
+ define :phone_numbers
12
+ define :add_phone_number do |area_code, number|
13
+ @phone_numbers << [area_code, number]
14
+ end
15
+ end
16
+ end
17
+
18
+ class User
19
+ def name()end
20
+ def phone_numbers()end
21
+ def add_phone_number()end
22
+ end
23
+
24
+ describe do
25
+ it 'ensures the mock lib looks like real lib' do
26
+ Mock::User.should substitute_for User
27
+ end
28
+
29
+ let(:user) { Mock::User.new }
30
+
31
+ example 'you can tell it how to behave and ask what happened with it' do
32
+ user.will_have_name "Sally"
33
+
34
+ user.should_not have_been_asked_for_its :name
35
+ user.name.should == "Sally"
36
+ user.should have_been_asked_for_its :name
37
+ end
38
+ end
39
+ ```
40
+
2
41
 
3
42
  TODO
4
43
  ----
@@ -1,6 +1,6 @@
1
1
  class Surrogate
2
2
 
3
- # adds surrogate behaviour to your class / singleton class / instances
3
+ # Adds surrogate behaviour to your class / singleton class / instances
4
4
  #
5
5
  # please refactor me!
6
6
  class Endower
@@ -22,29 +22,31 @@ class Surrogate
22
22
  private
23
23
 
24
24
  def endow_klass
25
- a_hatchery_for klass
25
+ add_hatchery_to klass
26
+ klass.extend ClassMethods
26
27
  enable_defining_methods klass
27
28
  record_initialization_for_instances_of klass
28
29
  remember_invocations_for_instances_of klass
29
30
  remember_invocations_for_instances_of klass.singleton_class
30
- hijack_instantiation_of klass
31
- can_get_a_new klass
32
31
  end
33
32
 
34
33
  def endow_singleton_class
35
- hatchery = a_hatchery_for singleton
34
+ hatchery = add_hatchery_to singleton
36
35
  enable_defining_methods singleton
37
36
  singleton.module_eval &playlist if playlist
38
- klass.instance_variable_set :@surrogate, Hatchling.new(klass, hatchery)
37
+ klass.instance_variable_set :@hatchling, Hatchling.new(klass, hatchery)
39
38
  klass
40
39
  end
41
40
 
42
- # yeesh :( try to find a better way to do this
41
+ # yeesh :( pretty sure there isn't a better way to do this
43
42
  def record_initialization_for_instances_of(klass)
44
43
  def klass.method_added(meth)
45
- return unless meth == :initialize && !@hijacking_initialize
44
+ return if meth != :initialize || @hijacking_initialize
46
45
  @hijacking_initialize = true
47
46
  current_initialize = instance_method :initialize
47
+
48
+ # `define' records the args while maintaining the old behaviour
49
+ # we have to do it stupidly like this because there is no to_proc on an unbound method
48
50
  define :initialize do |*args, &block|
49
51
  current_initialize.bind(self).call(*args, &block)
50
52
  end
@@ -61,48 +63,16 @@ class Surrogate
61
63
  klass.singleton_class
62
64
  end
63
65
 
64
- def can_get_a_new(klass)
65
- klass.extend Module.new {
66
- # use a module so that the method is inherited (important for substitutability)
67
- def clone
68
- new_klass = Class.new self
69
- surrogate = @surrogate
70
- Surrogate.endow new_klass do
71
- surrogate.api_methods.each do |method_name, options|
72
- define method_name, options.to_hash, &options.default_proc
73
- end
74
- end
75
- @hatchery.api_methods.each do |method_name, options|
76
- new_klass.define method_name, options.to_hash, &options.default_proc
77
- end
78
- new_klass
79
- end
80
- }
81
- end
82
-
83
66
  def remember_invocations_for_instances_of(klass)
84
67
  klass.send :define_method, :invocations do |method_name|
85
- @surrogate.invocations method_name
68
+ @hatchling.invocations method_name
86
69
  end
87
70
  end
88
71
 
89
- def a_hatchery_for(klass)
72
+ def add_hatchery_to(klass)
90
73
  klass.instance_variable_set :@hatchery, Surrogate::Hatchery.new(klass)
91
74
  end
92
75
 
93
- def hijack_instantiation_of(klass)
94
- # use a module so that the method is inherited (important for substitutability)
95
- klass.extend Module.new {
96
- def new(*args)
97
- instance = allocate
98
- hatchery = @hatchery
99
- instance.instance_eval { @surrogate = Hatchling.new instance, hatchery }
100
- instance.send :initialize, *args
101
- instance
102
- end
103
- }
104
- end
105
-
106
76
  def enable_defining_methods(klass)
107
77
  def klass.define(method_name, options={}, &block)
108
78
  @hatchery.define method_name, options, block
@@ -113,4 +83,26 @@ class Surrogate
113
83
  end
114
84
  end
115
85
  end
86
+
87
+
88
+ # use a module so that the method is inherited (important for substitutability)
89
+ module ClassMethods
90
+ def clone
91
+ hatchling, hatchery = @hatchling, @hatchery
92
+ Class.new self do
93
+ Surrogate.endow self do
94
+ hatchling.api_methods.each { |name, options| define name, options.to_hash, &options.default_proc }
95
+ end
96
+ hatchery.api_methods.each { |name, options| define name, options.to_hash, &options.default_proc }
97
+ end
98
+ end
99
+
100
+ # Custom new, because user can define initialize, and ivars should be set before it
101
+ def new(*args)
102
+ instance = allocate
103
+ instance.instance_variable_set :@hatchling, Hatchling.new(instance, @hatchery)
104
+ instance.send :initialize, *args
105
+ instance
106
+ end
107
+ end
116
108
  end
@@ -1,19 +1,22 @@
1
1
  class Surrogate
2
+
3
+ # This manages the definitions that were given to the class
4
+ # The hatchlings are added to the instances, and they look here
5
+ # to find out about how their methods are implemented.
2
6
  class Hatchery
3
7
  attr_accessor :klass
4
8
 
5
9
  def initialize(klass)
6
10
  self.klass = klass
7
- defines_methods
8
- end
9
-
10
- def defines_methods
11
- klass.singleton_class.send :define_method, :define, &method(:define)
11
+ klass_can_define_api_methods
12
12
  end
13
13
 
14
14
  def define(method_name, options={}, block)
15
- add_api_methods_for method_name
15
+ add_api_method_for method_name
16
+ add_verb_helpers_for method_name
17
+ add_noun_helpers_for method_name
16
18
  api_methods[method_name] = Options.new options, block
19
+ klass
17
20
  end
18
21
 
19
22
  def api_methods
@@ -24,24 +27,35 @@ class Surrogate
24
27
  api_methods.keys - [:initialize]
25
28
  end
26
29
 
27
- # here we need to find better domain terminology
28
- def add_api_methods_for(method_name)
30
+ private
31
+
32
+ def klass_can_define_api_methods
33
+ klass.singleton_class.send :define_method, :define, &method(:define)
34
+ end
35
+
36
+ def add_api_method_for(method_name)
29
37
  klass.send :define_method, method_name do |*args, &block|
30
- @surrogate.invoke_method method_name, args, &block
38
+ @hatchling.invoke_method method_name, args, &block
31
39
  end
40
+ end
41
+
42
+ def add_verb_helpers_for(method_name)
43
+ add_helpers_for method_name, "will_#{method_name}"
44
+ end
32
45
 
33
- # verbs
34
- klass.send :define_method, "will_#{method_name}" do |*args, &block|
46
+ def add_noun_helpers_for(method_name)
47
+ add_helpers_for method_name, "will_have_#{method_name}"
48
+ end
49
+
50
+ def add_helpers_for(method_name, helper_name)
51
+ klass.send :define_method, helper_name do |*args, &block|
35
52
  if args.size == 1
36
- @surrogate.prepare_method method_name, args, &block
53
+ @hatchling.prepare_method method_name, args, &block
37
54
  else
38
- @surrogate.prepare_method_queue method_name, args, &block
55
+ @hatchling.prepare_method_queue method_name, args, &block
39
56
  end
40
57
  self
41
58
  end
42
-
43
- # nouns
44
- klass.send :alias_method, "will_have_#{method_name}", "will_#{method_name}"
45
59
  end
46
60
  end
47
61
  end
@@ -1,5 +1,8 @@
1
1
  class Surrogate
2
2
  UnknownMethod = Class.new StandardError
3
+
4
+ # This contains the unique behaviour for each instance
5
+ # It handles method invocation and records the appropriate information
3
6
  class Hatchling
4
7
  attr_accessor :instance, :hatchery
5
8
 
@@ -15,6 +18,8 @@ class Surrogate
15
18
  invoked_methods[method_name] << args
16
19
  return get_default method_name, args unless has_ivar? method_name
17
20
  ivar = get_ivar method_name
21
+
22
+ # This may soon need classes for each type which know how to invoke themselves
18
23
  case ivar
19
24
  when MethodQueue
20
25
  play_from_queue ivar, method_name
@@ -228,10 +228,6 @@ class Surrogate
228
228
  use_case.match?
229
229
  end
230
230
 
231
- chain :nothing do
232
- use_case.expected_arguments = nothing
233
- end
234
-
235
231
  failure_message_for_should do
236
232
  use_case.failure_message_for_should
237
233
  end
@@ -1,24 +1,34 @@
1
1
  class Surrogate
2
- module RSpec
3
- module MessagesFor
4
- ::RSpec::Matchers.define :substitute_for do |original_class|
2
+ ::RSpec::Matchers.define :substitute_for do |original_class, options={}|
5
3
 
6
- comparison = nil
4
+ comparison = nil
5
+ subset_only = options[:subset]
7
6
 
8
- match do |mocked_class|
9
- comparison = ApiComparer.new(mocked_class, original_class).compare
10
- (comparison[:instance].values + comparison[:class].values).inject(:+).empty?
11
- end
7
+ match do |mocked_class|
8
+ comparison = ApiComparer.new(mocked_class, original_class).compare
9
+ if subset_only
10
+ (comparison[:instance][:not_on_actual] + comparison[:class][:not_on_actual]).empty?
11
+ else
12
+ (comparison[:instance].values + comparison[:class].values).inject(:+).empty?
13
+ end
14
+ end
12
15
 
13
- failure_message_for_should do
14
- "Should have been substitute, but found these differences #{comparison.inspect}"
15
- end
16
+ failure_message_for_should do
17
+ extra_instance_methods = comparison[:instance][:not_on_actual ].to_a
18
+ extra_class_methods = comparison[:class ][:not_on_actual ].to_a
19
+ missing_instance_methods = comparison[:instance][:not_on_surrogate].to_a
20
+ missing_class_methods = comparison[:class ][:not_on_surrogate].to_a
16
21
 
17
- failure_message_for_should_not do
18
- "Should not have been substitute, but was"
19
- end
20
- end
22
+ differences = []
23
+ differences << "has extra instance methods: #{extra_instance_methods.inspect}" if extra_instance_methods.any?
24
+ differences << "has extra class methods: #{extra_class_methods.inspect}" if extra_class_methods.any?
25
+ differences << "is missing instance methods: #{missing_instance_methods}" if !subset_only && missing_instance_methods.any?
26
+ differences << "is missing class methods: #{missing_class_methods}" if !subset_only && missing_class_methods.any?
27
+ "Was not substitutable because surrogate " << differences.join(', ')
28
+ end
29
+
30
+ failure_message_for_should_not do
31
+ "Should not have been substitute, but was"
21
32
  end
22
33
  end
23
34
  end
24
-
@@ -1,3 +1,3 @@
1
1
  class Surrogate
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -193,35 +193,34 @@ describe 'define' do
193
193
  specify 'even works with inheritance' do
194
194
  superclass = Class.new
195
195
  superclass.send(:define_method, :initialize) { @a = 1 }
196
- subclass = Class.new superclass
197
- mocked_subclass = Surrogate.endow Class.new subclass
198
- mocked_subclass.define :abc
199
- mocked_subclass.new.instance_variable_get(:@a).should == 1
196
+ subclass = Surrogate.endow Class.new superclass
197
+ subclass.define :abc
198
+ subclass.new.instance_variable_get(:@a).should == 1
200
199
  end
201
200
 
202
201
  context 'when not an api method' do
203
202
  it 'respects arity (this is probably 1.9.3 only)' do
204
- expect { mocked_class.new(1) }.to raise_error ArgumentError, 'wrong number of arguments(1 for 0)'
203
+ expect { mocked_class.new 1 }.to raise_error ArgumentError, 'wrong number of arguments(1 for 0)'
205
204
  end
206
205
 
207
- specify 'recorded regardless of when initialize is defined in relation to mock' do
208
- klass = Class.new do
209
- Surrogate.endow self
210
- def initialize(a)
211
- @a = a
206
+ describe 'invocations are recorded anyway' do
207
+ specify 'even when initialize is defined after surrogate block' do
208
+ klass = Class.new do
209
+ Surrogate.endow self
210
+ def initialize(a) @a = a end
212
211
  end
212
+ klass.new(1).should have_been_initialized_with 1
213
+ klass.new(1).instance_variable_get(:@a).should == 1
213
214
  end
214
- klass.new(1).should have_been_initialized_with 1
215
- klass.new(1).instance_variable_get(:@a).should == 1
216
215
 
217
- klass = Class.new do
218
- def initialize(a)
219
- @a = a
216
+ specify 'even when initialize is defined before surrogate block' do
217
+ klass = Class.new do
218
+ def initialize(a) @a = a end
219
+ Surrogate.endow self
220
220
  end
221
- Surrogate.endow self
221
+ klass.new(1).should have_been_initialized_with 1
222
+ klass.new(1).instance_variable_get(:@a).should == 1
222
223
  end
223
- klass.new(1).should have_been_initialized_with 1
224
- klass.new(1).instance_variable_get(:@a).should == 1
225
224
  end
226
225
  end
227
226
  end
@@ -1,72 +1,151 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'substitute_for' do
4
+ context 'exact substitutability' do
5
+ context "returns true iff api methods and inherited methods match exactly to the other object's methods. Examples:" do
6
+ context "a surrogate with no api methods" do
7
+ let(:surrogate) { Surrogate.endow Class.new }
8
+
9
+ example "is substitutable for a class with no methods" do
10
+ surrogate.should substitute_for Class.new
11
+ end
12
+
13
+ example "is not substitutable for a class with instance methods" do
14
+ surrogate.should_not substitute_for Class.new { def foo()end }
15
+ end
16
+
17
+ example "is not substitutable for a class with class methods" do
18
+ surrogate.should_not substitute_for Class.new { def self.foo()end }
19
+ end
20
+
21
+ example "is not substitutable for a class with inherited instance methods" do
22
+ parent = Class.new { def foo()end }
23
+ surrogate.should_not substitute_for Class.new(parent)
24
+ end
25
+
26
+ example "is not substitutable for a class with inherited class methods" do
27
+ parent = Class.new { def self.foo()end }
28
+ surrogate.should_not substitute_for Class.new(parent)
29
+ end
30
+ end
4
31
 
5
- context "returns true iff api methods and inherited methods match exactly to the other object's methods. Examples:" do
6
- context "a surrogate with no api methods" do
7
- let(:surrogate) { Surrogate.endow Class.new }
8
32
 
9
- example "is substitutable for a class with no methods" do
10
- surrogate.should substitute_for Class.new
11
- end
33
+ context "a surrogate with an instance level api method" do
34
+ let(:surrogate) { Class.new { Surrogate.endow self; define :foo } }
12
35
 
13
- example "is not substitutable for a class with instance methods" do
14
- surrogate.should_not substitute_for Class.new { def foo()end }
15
- end
36
+ example "is substitutable for a class with the same method" do
37
+ surrogate.should substitute_for Class.new { def foo()end }
38
+ end
16
39
 
17
- example "is not substitutable for a class with class methods" do
18
- surrogate.should_not substitute_for Class.new { def self.foo()end }
19
- end
40
+ example "is substitutable for a class that inherits the method" do
41
+ parent = Class.new { def foo()end }
42
+ surrogate.should substitute_for Class.new(parent)
43
+ end
20
44
 
21
- example "is not substitutable for a class with inherited instance methods" do
22
- parent = Class.new { def foo()end }
23
- surrogate.should_not substitute_for Class.new(parent)
24
- end
45
+ example "is not substitutable for a class without the method" do
46
+ surrogate.should_not substitute_for Class.new
47
+ end
25
48
 
26
- example "is not substitutable for a class with inherited class methods" do
27
- parent = Class.new { def self.foo()end }
28
- surrogate.should_not substitute_for Class.new(parent)
29
- end
30
- end
49
+ example "is not substitutable for a class with a different method" do
50
+ surrogate.should_not substitute_for Class.new { def bar()end }
51
+ end
31
52
 
53
+ example "is not substitutable for a class with additional methods" do
54
+ other = Class.new { def foo()end; def bar()end }
55
+ surrogate.should_not substitute_for other
56
+ end
32
57
 
33
- context "a surrogate with an instance level api method" do
34
- let(:surrogate) { Class.new { Surrogate.endow self; define :foo } }
58
+ example "is not substitutable for a class with the method and inerited additional methods" do
59
+ parent = Class.new { def bar()end }
60
+ surrogate.should_not substitute_for Class.new(parent) { def foo()end }
61
+ end
35
62
 
36
- example "is substitutable for a class with the same method" do
37
- surrogate.should substitute_for Class.new { def foo()end }
38
- end
63
+ example "is not substitutable for a class with the method and additional class methods" do
64
+ surrogate.should_not substitute_for Class.new { def foo()end; def self.bar()end }
65
+ end
39
66
 
40
- example "is substitutable for a class that inherits the method" do
41
- parent = Class.new { def foo()end }
42
- surrogate.should substitute_for Class.new(parent)
67
+ example "is not substitutable for a class with the method and inherited additional class methods" do
68
+ parent = Class.new { def self.bar()end }
69
+ surrogate.should_not substitute_for Class.new(parent) { def foo()end }
70
+ end
43
71
  end
44
72
 
45
- example "is not substitutable for a class without the method" do
46
- surrogate.should_not substitute_for Class.new
73
+
74
+ describe "it has helpful error messages" do
75
+ let(:surrogate) { Surrogate.endow Class.new }
76
+
77
+ specify 'when klass is missing an instance method' do
78
+ surrogate.define :meth
79
+ expect { surrogate.should substitute_for Class.new }.to \
80
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate has extra instance methods: [:meth]")
81
+ end
82
+
83
+ specify 'when klass is missing a class method' do
84
+ surrogate = Surrogate.endow(Class.new) { define :meth }
85
+ expect { surrogate.should substitute_for Class.new }.to \
86
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate has extra class methods: [:meth]")
87
+ end
88
+
89
+ specify 'when surrogate is missing an instance method' do
90
+ klass = Class.new { def meth() end }
91
+ expect { surrogate.should substitute_for klass }.to \
92
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate is missing instance methods: [:meth]")
93
+ end
94
+
95
+ specify 'when surrogate is missing a class method' do
96
+ klass = Class.new { def self.meth() end }
97
+ expect { surrogate.should substitute_for klass }.to \
98
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate is missing class methods: [:meth]")
99
+ end
100
+
101
+ specify 'when combined' do
102
+ surrogate = Surrogate.endow(Class.new) { define :surrogate_class_meth }.define :surrogate_instance_meth
103
+ klass = Class.new { def self.api_class_meth()end; def api_instance_meth() end }
104
+ expect { surrogate.should substitute_for klass }.to \
105
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate has extra instance methods: [:surrogate_instance_meth], "\
106
+ "has extra class methods: [:surrogate_class_meth], "\
107
+ "is missing instance methods: [:api_instance_meth], "\
108
+ "is missing class methods: [:api_class_meth]")
109
+ end
110
+
111
+ specify "when negated (idk why you'd ever want this, though)" do
112
+ expect { surrogate.should_not substitute_for Class.new }.to \
113
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Should not have been substitute, but was")
114
+ end
47
115
  end
116
+ end
117
+ end
118
+
119
+
48
120
 
49
- example "is not substitutable for a class with a different method" do
50
- surrogate.should_not substitute_for Class.new { def bar()end }
121
+ context 'subset substitutability -- specified with subset: true option' do
122
+ context "returns true if api methods and inherited methods match are all implemented by other class. Examples:" do
123
+ example 'true when exact match' do
124
+ Surrogate.endow(Class.new).should substitute_for Class.new, subset: true
51
125
  end
52
126
 
53
- example "is not substitutable for a class with additional methods" do
54
- other = Class.new { def foo()end; def bar()end }
55
- surrogate.should_not substitute_for other
127
+ example 'true when other has additional instance methods and class methods' do
128
+ klass = Class.new { def self.class_meth()end; def instance_meth()end }
129
+ Surrogate.endow(Class.new).should substitute_for klass, subset: true
56
130
  end
57
131
 
58
- example "is not substitutable for a class with the method and inerited additional methods" do
59
- parent = Class.new { def bar()end }
60
- surrogate.should_not substitute_for Class.new(parent) { def foo()end }
132
+ example 'false when other is missing instance methods' do
133
+ klass = Class.new { def self.extra_method()end; def extra_method()end }
134
+ expect { Surrogate.endow(Class.new).define(:meth).should substitute_for klass, subset:true }.to \
135
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate has extra instance methods: [:meth]")
61
136
  end
62
137
 
63
- example "is not substitutable for a class with the method and additional class methods" do
64
- surrogate.should_not substitute_for Class.new { def foo()end; def self.bar()end }
138
+ example 'false when other is missing class methods' do
139
+ klass = Class.new { def self.extra_method()end; def extra_method()end }
140
+ expect { Surrogate.endow(Class.new) { define :meth }.should substitute_for klass, subset:true }.to \
141
+ raise_error(RSpec::Expectations::ExpectationNotMetError, "Was not substitutable because surrogate has extra class methods: [:meth]")
65
142
  end
66
143
 
67
- example "is not substitutable for a class with the method and inherited additional class methods" do
68
- parent = Class.new { def self.bar()end }
69
- surrogate.should_not substitute_for Class.new(parent) { def foo()end }
144
+ example 'false when other is missing instance and class methods' do
145
+ klass = Class.new { def self.extra_method()end; def extra_method()end }
146
+ expect { Surrogate.endow(Class.new) { define :class_meth }.define(:instance_meth).should substitute_for klass, subset: true }.to \
147
+ raise_error(RSpec::Expectations::ExpectationNotMetError,
148
+ "Was not substitutable because surrogate has extra instance methods: [:instance_meth], has extra class methods: [:class_meth]")
70
149
  end
71
150
  end
72
151
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surrogate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-20 00:00:00.000000000 Z
12
+ date: 2012-04-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70270582700600 !ruby/object:Gem::Requirement
16
+ requirement: &70298496271040 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 2.8.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70270582700600
24
+ version_requirements: *70298496271040
25
25
  description: Framework to aid in handrolling mock/spy objects.
26
26
  email:
27
27
  - josh.cheek@gmail.com