derickbailey-notamock 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +129 -0
- data/Rakefile +38 -0
- data/TODO +49 -0
- data/VERSION +1 -0
- data/lib/not_a_mock.rb +15 -0
- data/lib/not_a_mock/active_record_extensions.rb +12 -0
- data/lib/not_a_mock/argument_constraint_extensions.rb +32 -0
- data/lib/not_a_mock/call_recorder.rb +93 -0
- data/lib/not_a_mock/matchers.rb +68 -0
- data/lib/not_a_mock/matchers/anything_matcher.rb +28 -0
- data/lib/not_a_mock/matchers/args_matcher.rb +74 -0
- data/lib/not_a_mock/matchers/call_matcher.rb +64 -0
- data/lib/not_a_mock/matchers/method_matcher.rb +29 -0
- data/lib/not_a_mock/matchers/result_matcher.rb +29 -0
- data/lib/not_a_mock/matchers/times_matcher.rb +46 -0
- data/lib/not_a_mock/object_extensions.rb +107 -0
- data/lib/not_a_mock/rspec_mock_framework_adapter.rb +16 -0
- data/lib/not_a_mock/stub.rb +40 -0
- data/lib/not_a_mock/stubber.rb +85 -0
- data/lib/not_a_mock/stubmethod.rb +26 -0
- data/spec/call_recording_spec.rb +68 -0
- data/spec/matchers_spec.rb +217 -0
- data/spec/stub_active_record_spec.rb +29 -0
- data/spec/stub_instance_spec.rb +49 -0
- data/spec/stub_method_spec.rb +242 -0
- data/spec/stub_method_with_block_spec.rb +181 -0
- metadata +96 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Peter Yandell
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
= Not A Mock
|
2
|
+
|
3
|
+
A cleaner and DRYer alternative to mocking and stubbing with RSpec.
|
4
|
+
|
5
|
+
http://notahat.com/not_a_mock
|
6
|
+
|
7
|
+
== A Quick Introduction
|
8
|
+
|
9
|
+
=== Mocking (Not)
|
10
|
+
|
11
|
+
When you're setting up for a spec, you can ask that method calls on an object be recorded:
|
12
|
+
|
13
|
+
object.track_methods(:name, :length)
|
14
|
+
|
15
|
+
Once your code has run, you can make assertions about what methods were called, what arguments
|
16
|
+
they took, their results, etc.
|
17
|
+
|
18
|
+
object.should have_received(:length).without_args.and_returned(42)
|
19
|
+
object.should have_received(:name).twice
|
20
|
+
|
21
|
+
See NotAMock::Matchers for an explanation of the available assertions, plus
|
22
|
+
Object#track_methods and Object#untrack_methods.
|
23
|
+
|
24
|
+
=== Stubbing
|
25
|
+
|
26
|
+
==== Stubbing Methods
|
27
|
+
|
28
|
+
You can replace a method on an object with a stub version like this:
|
29
|
+
|
30
|
+
object.stub_method(:method => return_value)
|
31
|
+
|
32
|
+
Any call to +method+ after this will return +return_value+ without invoking
|
33
|
+
the method's usual code.
|
34
|
+
|
35
|
+
Calls to stub methods are recorded as if you had called +track_methods+
|
36
|
+
on them, so you can make assertions about them as shown above.
|
37
|
+
|
38
|
+
See Object#stub_methods, Object#unstub_methods.
|
39
|
+
|
40
|
+
==== Stubbing Objects
|
41
|
+
|
42
|
+
You can also replace an entire object with a stub version like this:
|
43
|
+
|
44
|
+
my_object = MyClass.stub_instance(:method_a => return_value, :method_b => return_value, ...)
|
45
|
+
|
46
|
+
The returned +my_object+ is a stub instance of MyClass with the given methods
|
47
|
+
defined to provide the corresponding return values.
|
48
|
+
|
49
|
+
See Object.stub_instance.
|
50
|
+
|
51
|
+
==== Stubbing ActiveRecord Instances
|
52
|
+
|
53
|
+
When you call +stub_instance+ on an ActiveRecord::Base subclass,
|
54
|
+
Not A Mock automatically provides an +id+ method and generates an
|
55
|
+
id for the object.
|
56
|
+
|
57
|
+
=== Yielding To Method Blocks
|
58
|
+
|
59
|
+
There are many situation in which a stubbed method will be called with a block attached to it. For example,
|
60
|
+
if you are using the Net-SSH gem, you may have code like this:
|
61
|
+
|
62
|
+
Net::SSH.start(@server, @username, :password => @password) do |ssh|
|
63
|
+
ssh.exec!(cmd)
|
64
|
+
end
|
65
|
+
|
66
|
+
To correctly stub and test the SSH.start method and the ssh.exec! method, you need to yield an object to the
|
67
|
+
block code, that supports a method called "exec!".
|
68
|
+
|
69
|
+
To do this, you can call "yields(*args)" on a stubbed method. For example, you can can stub the SSH.start method
|
70
|
+
and the exec! method, like this:
|
71
|
+
|
72
|
+
@sshstub = Net::SSH::Connection::Session.stub_instance(:exec! => nil)
|
73
|
+
Net::SSH.stub_method(:start).yields(@sshstub)
|
74
|
+
|
75
|
+
When the .exec! method is called from withing the code block of SSH.start, the @sshstub object will be yielded to the
|
76
|
+
block, allowing you to track the method, provide a stubbed block of code to execute, etc.
|
77
|
+
|
78
|
+
In situations where you are calling .yields(*args), you can still provide a stub method block to replace the code
|
79
|
+
that is executed when the stub method is called. There are multiple ways of doing this:
|
80
|
+
|
81
|
+
Net::SSH.stub_method(:start).yields(@sshstub) { puts 'this is the method stub code for .start' }
|
82
|
+
|
83
|
+
or
|
84
|
+
|
85
|
+
stub_block = lambda { puts 'this is the method stub code for .start' }
|
86
|
+
Net::SSH.stub_method(:start, &stub_block).yields(@sshstub)
|
87
|
+
|
88
|
+
Both of these syntax forms will provide the exact same functionality - the block that puts the string of information
|
89
|
+
will be executed when you call 'Net::SSH.start'
|
90
|
+
|
91
|
+
== Installation
|
92
|
+
|
93
|
+
(The following describes using NotAMock with Rails. It should also be possible
|
94
|
+
to use it outside of Rails, but I haven't tried it.)
|
95
|
+
|
96
|
+
First, install the rspec and rspec_on_rails plugins:
|
97
|
+
|
98
|
+
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec
|
99
|
+
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails
|
100
|
+
ruby script/generate rspec
|
101
|
+
|
102
|
+
(See http://rspec.info/documentation/rails/install.html for more details.)
|
103
|
+
|
104
|
+
Second, install the not_a_mock plugin:
|
105
|
+
|
106
|
+
ruby script/plugin install git://github.com/notahat/not_a_mock.git
|
107
|
+
|
108
|
+
Finally, add the following to your project's spec/spec_helper.rb:
|
109
|
+
|
110
|
+
config.mock_with NotAMock::RspecMockFrameworkAdapter
|
111
|
+
|
112
|
+
==== Installation via Ruby Gems
|
113
|
+
|
114
|
+
At the moment, there is no hosted gem for not_a_mock. However, you can easily build and install the gem from the source
|
115
|
+
directly. To do so, follow these instructions:
|
116
|
+
|
117
|
+
1. clone the source code from git://github.com/derickbailey/not_a_mock.git
|
118
|
+
2. run 'rake jeweler:gemspec' in the root folder of your not_a_mock clone
|
119
|
+
3. run 'rake jeweler:build' in the root folder of your not_a_mock clone
|
120
|
+
4. run 'gem install -l pkg/NotAMock-#.#.#.gem' where #.#.# is the version number produced in step 3
|
121
|
+
|
122
|
+
After this you will be able to "include 'not_a_mock'" in your rspec tests, and configure rspec as shown for spec_helper.rb,
|
123
|
+
above.
|
124
|
+
|
125
|
+
== Contributing
|
126
|
+
|
127
|
+
Send bugs, patches, and suggestions to Pete Yandell (pete@notahat.com)
|
128
|
+
|
129
|
+
Thanks to Derick Bailey, Pat Allan and Steve Hayes for contributing patches.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc "Default: run specs"
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc "Run all the specs for the notamock plugin."
|
9
|
+
Spec::Rake::SpecTask.new do |t|
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
t.spec_opts = ['--colour']
|
12
|
+
# t.rcov = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Generate documentation for the notamock plugin."
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'NotAMock'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('MIT-LICENSE')
|
22
|
+
rdoc.rdoc_files.include('TODO')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
25
|
+
|
26
|
+
namespace :jeweler do
|
27
|
+
require 'jeweler'
|
28
|
+
Jeweler::Tasks.new do |gs|
|
29
|
+
gs.name = "derickbailey-notamock"
|
30
|
+
gs.summary = "A cleaner and DRYer alternative to mocking and stubbing with RSpec"
|
31
|
+
gs.description = "A cleaner and DRYer alternative to mocking and stubbing with RSpec, using Arrange-Act-Assert syntax"
|
32
|
+
gs.homepage = "http://github.com/derickbailey/not_a_mock"
|
33
|
+
gs.authors = ["Pete Yandell", "Derick Bailey"]
|
34
|
+
gs.has_rdoc = false
|
35
|
+
gs.files.exclude("*.gemspec", ".gitignore")
|
36
|
+
gs.add_dependency('rspec', '>= 1.2.9')
|
37
|
+
end
|
38
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
== To Do
|
2
|
+
|
3
|
+
=== Recording calls that throw exceptions
|
4
|
+
|
5
|
+
Currently calls to methods that throw exceptions are not recorded. It would
|
6
|
+
be good if they were, and if assertions could be made against them, e.g.
|
7
|
+
|
8
|
+
object.should have_received(:gsub).and_raised(ArgumentError)
|
9
|
+
|
10
|
+
=== Stubbing and recording of methods that take blocks
|
11
|
+
|
12
|
+
You can't stub or record calls for methods that take blocks. This is very
|
13
|
+
difficult to fix in Ruby 1.8, but will be much easier in 1.9.
|
14
|
+
|
15
|
+
=== Better Error Messages
|
16
|
+
|
17
|
+
Error messages are pretty smart already. If I call:
|
18
|
+
|
19
|
+
object.should have_received(:gsub).with("Hello", "Goodbye").and_returned("Goodbye, world!")
|
20
|
+
|
21
|
+
I might get an error like:
|
22
|
+
|
23
|
+
Object received gsub, but not with arguments ["Hello", "Goodbye"].
|
24
|
+
|
25
|
+
It would be much more useful if the error message told you the arguments
|
26
|
+
it actually received as well.
|
27
|
+
|
28
|
+
=== Order Assertions
|
29
|
+
|
30
|
+
I should be able to write something like this to assert that the calls
|
31
|
+
happened in order:
|
32
|
+
|
33
|
+
in_order do
|
34
|
+
object_a.should have_received(:message_a)
|
35
|
+
object_b.should have_received(:message_b)
|
36
|
+
object_a.should have_received(:message_c)
|
37
|
+
end
|
38
|
+
|
39
|
+
=== Smarter ActiveRecord stubbing
|
40
|
+
|
41
|
+
I often find myself doing something like this before a test:
|
42
|
+
|
43
|
+
@comment = Comment.stub_object(:body => "Great!")
|
44
|
+
@comments = Object.stub_object(:find => [@comment])
|
45
|
+
@article = Article.stub_object(:title => "Hello, world!", :comments => @comments)
|
46
|
+
Article.stub_method(:find => @article)
|
47
|
+
|
48
|
+
I'd like to find a way to make this neater, auto-generate the stub
|
49
|
+
relationship, etc.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/not_a_mock.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'not_a_mock/rspec_mock_framework_adapter'
|
2
|
+
require 'not_a_mock/active_record_extensions'
|
3
|
+
require 'not_a_mock/argument_constraint_extensions'
|
4
|
+
require 'not_a_mock/call_recorder'
|
5
|
+
require 'not_a_mock/matchers'
|
6
|
+
require 'not_a_mock/matchers/anything_matcher'
|
7
|
+
require 'not_a_mock/matchers/args_matcher'
|
8
|
+
require 'not_a_mock/matchers/call_matcher'
|
9
|
+
require 'not_a_mock/matchers/method_matcher'
|
10
|
+
require 'not_a_mock/matchers/result_matcher'
|
11
|
+
require 'not_a_mock/matchers/times_matcher'
|
12
|
+
require 'not_a_mock/object_extensions'
|
13
|
+
require 'not_a_mock/stubber'
|
14
|
+
require 'not_a_mock/stubmethod'
|
15
|
+
require 'not_a_mock/stub'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveRecord # :nodoc:
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def self.stub_instance(methods = {})
|
5
|
+
@@__stub_object_id ||= 1000
|
6
|
+
@@__stub_object_id += 1
|
7
|
+
methods = methods.merge(:id => @@__stub_object_id, :to_param => @@__stub_object_id.to_s)
|
8
|
+
NotAMock::Stub.new(self, methods)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Spec #:nodoc:
|
4
|
+
module Mocks #:nodoc:
|
5
|
+
class AnyOrderArgConstraint #:nodoc:
|
6
|
+
def initialize(array)
|
7
|
+
@array = array
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(arg)
|
11
|
+
Set.new(@array) == Set.new(arg)
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"in_any_order(#{@array.inspect})"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ArgumentMatchers #:nodoc:
|
20
|
+
class AnyArgMatcher #:nodoc:
|
21
|
+
def inspect
|
22
|
+
'anything'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def in_any_order(array)
|
27
|
+
Spec::Mocks::AnyOrderArgConstraint.new(array)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'not_a_mock/object_extensions'
|
3
|
+
|
4
|
+
module NotAMock
|
5
|
+
# The CallRecorder is a singleton that keeps track of all the call
|
6
|
+
# recording hooks installed, and keeps a central record of calls.
|
7
|
+
class CallRecorder
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@calls = []
|
12
|
+
@tracked_methods = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# An array of recorded calls in chronological order.
|
16
|
+
#
|
17
|
+
# Each call is represented by a hash, in this format:
|
18
|
+
# { :object => example_object, :method => :example_method, :args => ["example argument"], :result => "example result" }
|
19
|
+
attr_reader :calls
|
20
|
+
|
21
|
+
# Return an array of all the calls made to any method of +object+, in chronological order.
|
22
|
+
def calls_by_object(object)
|
23
|
+
@calls.select {|call| call[:object] == object }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return an array of all the calls made to +method+ of +object+, in chronological order.
|
27
|
+
def calls_by_object_and_method(object, method)
|
28
|
+
@calls.select {|call| call[:object] == object && call[:method] == method }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Patch +object+ so that future calls to +method+ will be recorded.
|
32
|
+
#
|
33
|
+
# You should call Object#track_methods rather than calling this directly.
|
34
|
+
def track_method(object, method) #:nodoc:
|
35
|
+
unless @tracked_methods.include?([object, method])
|
36
|
+
@tracked_methods << [object, method]
|
37
|
+
add_hook(object, method)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Remove the patch from +object+ so that future calls to +method+
|
42
|
+
# will not be recorded.
|
43
|
+
#
|
44
|
+
# You should call Object#track_methods rather than calling this directly.
|
45
|
+
def untrack_method(object, method) #:nodoc:
|
46
|
+
if @tracked_methods.delete([object, method])
|
47
|
+
remove_hook(object, method)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Stop recording all calls.
|
52
|
+
def untrack_all
|
53
|
+
@tracked_methods.each do |object, method|
|
54
|
+
remove_hook(object, method)
|
55
|
+
end
|
56
|
+
@tracked_methods = []
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remove all patches so that calls are no longer recorded, and clear the call log.
|
60
|
+
def reset
|
61
|
+
untrack_all
|
62
|
+
@calls = []
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def add_hook(object, method)
|
68
|
+
object.meta_eval do
|
69
|
+
alias_method("__unlogged_#{method}", method)
|
70
|
+
end
|
71
|
+
object.instance_eval(<<-EOF, __FILE__, __LINE__)
|
72
|
+
def #{method} (*args, &block)
|
73
|
+
return_value = NotAMock::CallRecorder.instance.send(:record_and_send, self, :#{method}, args, &block)
|
74
|
+
return_value
|
75
|
+
end
|
76
|
+
EOF
|
77
|
+
end
|
78
|
+
|
79
|
+
def record_and_send(object, method, args, &block)
|
80
|
+
result = object.send("__unlogged_#{method}", *args, &block)
|
81
|
+
@calls << { :object => object, :method => method, :args => args, :result => result }
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove_hook(object, method)
|
86
|
+
object.meta_eval do
|
87
|
+
alias_method(method, "__unlogged_#{method}")
|
88
|
+
remove_method("__unlogged_#{method}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module NotAMock
|
2
|
+
# == Message Assertions
|
3
|
+
#
|
4
|
+
# You can assert that an object should have received particular messages:
|
5
|
+
#
|
6
|
+
# object.should have_received(:message)
|
7
|
+
# object.should_not have_received(:message)
|
8
|
+
#
|
9
|
+
# Further restrictions cannot be added after +should_not have_received+.
|
10
|
+
#
|
11
|
+
# You can also make general assertions about whether an object should have
|
12
|
+
# received any messages:
|
13
|
+
#
|
14
|
+
# object.should have_been_called
|
15
|
+
# object.should_not have_been_called
|
16
|
+
#
|
17
|
+
# == Argument Assertions
|
18
|
+
#
|
19
|
+
# You can assert that a call was made with or without arguments:
|
20
|
+
#
|
21
|
+
# object.should have_received(:message).with(arg1, arg2, ...)
|
22
|
+
# object.should have_received(:message).without_args
|
23
|
+
#
|
24
|
+
# See NotAMock::Matchers::ArgsMatcher for more information.
|
25
|
+
#
|
26
|
+
# == Return Value Assertions
|
27
|
+
#
|
28
|
+
# object.should have_received(:message).and_returned(return_value)
|
29
|
+
# object.should have_received(:message).with(arg1, arg2, ...).and_returned(return_value)
|
30
|
+
#
|
31
|
+
# == Count Assertions
|
32
|
+
#
|
33
|
+
# object.should have_received(:message).once
|
34
|
+
# object.should have_received(:message).with(arg1, arg2, ...).twice
|
35
|
+
# object.should have_received(:message).with(arg1, arg2, ...).and_returned(return_value).exactly(n).times
|
36
|
+
# object.should have_been_called.exactly(n).times
|
37
|
+
#
|
38
|
+
# Any count specifier must go at the end of the expression.
|
39
|
+
#
|
40
|
+
# The exception to this rule is +once+, which allows things like this:
|
41
|
+
#
|
42
|
+
# object.should have_received(:message).once.with(arg1, arg2, ...).and_returned(return_value)
|
43
|
+
#
|
44
|
+
# which verifies that +message+ was called only once, and that call had
|
45
|
+
# the given arguments and return value.
|
46
|
+
#
|
47
|
+
# Note that this is subtly different from:
|
48
|
+
#
|
49
|
+
# object.should have_received(:message).with(arg1, arg2, ...).and_returned(return_value).once
|
50
|
+
#
|
51
|
+
# which verifies that +message+ was called with the given arguments and
|
52
|
+
# return value only once. It may, however, have been called with different
|
53
|
+
# arguments and return value at other times.
|
54
|
+
#
|
55
|
+
# == Assertion Failures
|
56
|
+
#
|
57
|
+
# FIXME: Write some docs about the nice error messages.
|
58
|
+
module Matchers
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def have_been_called
|
63
|
+
NotAMock::Matchers::AnythingMatcher.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def have_received(method)
|
67
|
+
NotAMock::Matchers::MethodMatcher.new(method)
|
68
|
+
end
|