not_a_mock 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +82 -0
- data/Rakefile +38 -0
- data/TODO +49 -0
- data/VERSION +1 -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 +88 -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/matchers.rb +68 -0
- data/lib/not_a_mock/object_extensions.rb +105 -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 +69 -0
- data/lib/not_a_mock.rb +14 -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
- metadata +85 -0
data/.gitignore
ADDED
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,82 @@
|
|
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
|
+
== Installation
|
58
|
+
|
59
|
+
(The following describes using NotAMock with Rails. It should also be possible
|
60
|
+
to use it outside of Rails, but I haven't tried it.)
|
61
|
+
|
62
|
+
First, install the rspec and rspec_on_rails plugins:
|
63
|
+
|
64
|
+
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec
|
65
|
+
ruby script/plugin install http://rspec.rubyforge.org/svn/tags/CURRENT/rspec_on_rails
|
66
|
+
ruby script/generate rspec
|
67
|
+
|
68
|
+
(See http://rspec.info/documentation/rails/install.html for more details.)
|
69
|
+
|
70
|
+
Second, install the not_a_mock plugin:
|
71
|
+
|
72
|
+
ruby script/plugin install git://github.com/notahat/not_a_mock.git
|
73
|
+
|
74
|
+
Finally, add the following to your project's spec/spec_helper.rb:
|
75
|
+
|
76
|
+
config.mock_with NotAMock::RspecMockFrameworkAdapter
|
77
|
+
|
78
|
+
== Contributing
|
79
|
+
|
80
|
+
Send bugs, patches, and suggestions to Pete Yandell (pete@notahat.com)
|
81
|
+
|
82
|
+
Thanks to 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
|
+
begin
|
27
|
+
require 'jeweler'
|
28
|
+
Jeweler::Tasks.new do |gemspec|
|
29
|
+
gemspec.name = "not_a_mock"
|
30
|
+
gemspec.summary = "A cleaner and DRYer alternative to mocking and stubbing with RSpec"
|
31
|
+
gemspec.email = "pete@notahat.com"
|
32
|
+
gemspec.homepage = "http://notahat.com/not_a_mock"
|
33
|
+
gemspec.authors = ["Pete Yandell"]
|
34
|
+
end
|
35
|
+
Jeweler::GemcutterTasks.new
|
36
|
+
rescue LoadError
|
37
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
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
|
+
1.0.0
|
@@ -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,88 @@
|
|
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
|
+
define_method(method) {|*args| CallRecorder.instance.send(:record_and_send, self, method, args) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def record_and_send(object, method, args)
|
75
|
+
result = object.send("__unlogged_#{method}", *args)
|
76
|
+
@calls << { :object => object, :method => method, :args => args, :result => result }
|
77
|
+
result
|
78
|
+
end
|
79
|
+
|
80
|
+
def remove_hook(object, method)
|
81
|
+
object.meta_eval do
|
82
|
+
alias_method(method, "__unlogged_#{method}")
|
83
|
+
remove_method("__unlogged_#{method}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'not_a_mock/matchers/call_matcher'
|
2
|
+
|
3
|
+
module NotAMock
|
4
|
+
module Matchers
|
5
|
+
# Matcher for
|
6
|
+
# object.should have_been_called
|
7
|
+
class AnythingMatcher < CallMatcher
|
8
|
+
|
9
|
+
def initialize(parent = nil)
|
10
|
+
super parent
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches_without_parents?
|
14
|
+
@calls = CallRecorder.instance.calls_by_object(@object)
|
15
|
+
!@calls.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure_message_without_parents
|
19
|
+
if matched?
|
20
|
+
" was called"
|
21
|
+
else
|
22
|
+
" was never called"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'not_a_mock/matchers/call_matcher'
|
2
|
+
|
3
|
+
module NotAMock
|
4
|
+
module Matchers
|
5
|
+
# Matcher for
|
6
|
+
# with(...)
|
7
|
+
#
|
8
|
+
# == Argument Matchers
|
9
|
+
#
|
10
|
+
# Not A Mock supports the use of RSpec's patterns for argument matching
|
11
|
+
# in mocks, and extends them. The most useful are listed below.
|
12
|
+
#
|
13
|
+
# === Anything Matcher
|
14
|
+
#
|
15
|
+
# The +anything+ pattern will match any value. For example:
|
16
|
+
#
|
17
|
+
# object.should have_received(:message).with(1, anything, 3)
|
18
|
+
#
|
19
|
+
# will match the following calls:
|
20
|
+
#
|
21
|
+
# object.message(1, 2, 3)
|
22
|
+
# object.message(1, 'Boo!', 3)
|
23
|
+
#
|
24
|
+
# but not:
|
25
|
+
#
|
26
|
+
# object.message(3, 2, 1)
|
27
|
+
# object.message(1, 2, 3, 4)
|
28
|
+
#
|
29
|
+
# === In Any Order Matcher
|
30
|
+
#
|
31
|
+
# The +in_any_order+ pattern will match an array argument, but won't care
|
32
|
+
# about order of elements in the array. For example:
|
33
|
+
#
|
34
|
+
# object.should have_received(:message).with(in_any_order([3, 2, 1]))
|
35
|
+
#
|
36
|
+
# will match the following calls:
|
37
|
+
#
|
38
|
+
# object.message([3, 2, 1])
|
39
|
+
# object.message([1, 2, 3])
|
40
|
+
#
|
41
|
+
# but not:
|
42
|
+
#
|
43
|
+
# object.message([1, 2, 3, 4])
|
44
|
+
class ArgsMatcher < CallMatcher
|
45
|
+
|
46
|
+
def initialize(args, parent = nil)
|
47
|
+
super parent
|
48
|
+
@args = args
|
49
|
+
end
|
50
|
+
|
51
|
+
def matches_without_parents?
|
52
|
+
@calls = @parent.calls.select {|entry| @args == entry[:args] }
|
53
|
+
!@calls.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def failure_message_without_parents
|
57
|
+
if matched?
|
58
|
+
if @args.empty?
|
59
|
+
", without args"
|
60
|
+
else
|
61
|
+
", with args #{@args.inspect}"
|
62
|
+
end
|
63
|
+
else
|
64
|
+
if @args.empty?
|
65
|
+
", but not without args"
|
66
|
+
else
|
67
|
+
", but not with args #{@args.inspect}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module NotAMock
|
2
|
+
module Matchers
|
3
|
+
class CallMatcher
|
4
|
+
|
5
|
+
def initialize(parent = nil)
|
6
|
+
@parent = parent
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(object)
|
10
|
+
@object = object
|
11
|
+
@matched = parent_matches? && matches_without_parents?
|
12
|
+
end
|
13
|
+
|
14
|
+
def matched?; @matched end
|
15
|
+
|
16
|
+
attr_reader :calls
|
17
|
+
|
18
|
+
def failure_message
|
19
|
+
if parent_matched?
|
20
|
+
parent_failure_message + failure_message_without_parents
|
21
|
+
else
|
22
|
+
parent_failure_message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def negative_failure_message
|
27
|
+
failure_message
|
28
|
+
end
|
29
|
+
|
30
|
+
def with(*args)
|
31
|
+
ArgsMatcher.new(args, self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def without_args
|
35
|
+
ArgsMatcher.new([], self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def and_returned(result)
|
39
|
+
ResultMatcher.new(result, self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def exactly(n)
|
43
|
+
TimesMatcher.new(n, self)
|
44
|
+
end
|
45
|
+
def once; exactly(1) end
|
46
|
+
def twice; exactly(2) end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def parent_matches?
|
51
|
+
@parent.nil? || @parent.matches?(@object)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parent_matched?
|
55
|
+
@parent.nil? || @parent.matched?
|
56
|
+
end
|
57
|
+
|
58
|
+
def parent_failure_message
|
59
|
+
@parent ? @parent.failure_message : @object.inspect
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'not_a_mock/matchers/call_matcher'
|
2
|
+
|
3
|
+
module NotAMock
|
4
|
+
module Matchers
|
5
|
+
# Matcher for
|
6
|
+
# object.should have_received(...)
|
7
|
+
class MethodMatcher < CallMatcher
|
8
|
+
|
9
|
+
def initialize(method, parent = nil)
|
10
|
+
super parent
|
11
|
+
@method = method
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches_without_parents?
|
15
|
+
@calls = CallRecorder.instance.calls_by_object_and_method(@object, @method)
|
16
|
+
!@calls.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message_without_parents
|
20
|
+
if matched?
|
21
|
+
" received #{@method}"
|
22
|
+
else
|
23
|
+
" didn't receive #{@method}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'not_a_mock/matchers/call_matcher'
|
2
|
+
|
3
|
+
module NotAMock
|
4
|
+
module Matchers
|
5
|
+
# Matcher for
|
6
|
+
# and_returned(...)
|
7
|
+
class ResultMatcher < CallMatcher
|
8
|
+
|
9
|
+
def initialize(result, parent = nil)
|
10
|
+
super parent
|
11
|
+
@result = result
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches_without_parents?
|
15
|
+
@calls = @parent.calls.select {|entry| entry[:result] == @result }
|
16
|
+
!@calls.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message_without_parents
|
20
|
+
if matched?
|
21
|
+
", and returned #{@result.inspect}"
|
22
|
+
else
|
23
|
+
", but didn't return #{@result.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'not_a_mock/matchers/call_matcher'
|
2
|
+
|
3
|
+
module NotAMock
|
4
|
+
module Matchers
|
5
|
+
# Matcher for +once+, +twice+, and
|
6
|
+
# exactly(n).times
|
7
|
+
class TimesMatcher < CallMatcher
|
8
|
+
|
9
|
+
def initialize(times, parent = nil)
|
10
|
+
super parent
|
11
|
+
@times = times
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches_without_parents?
|
15
|
+
@calls = @parent.calls
|
16
|
+
@calls.length == @times
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message_without_parents
|
20
|
+
if matched?
|
21
|
+
", #{times_in_english(@parent.calls.length)}"
|
22
|
+
else
|
23
|
+
", but #{times_in_english(@parent.calls.length, true)}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def times
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def times_in_english(times, only = false)
|
34
|
+
case times
|
35
|
+
when 1
|
36
|
+
only ? "only once" : "once"
|
37
|
+
when 2
|
38
|
+
"twice"
|
39
|
+
else
|
40
|
+
"#{times} times"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
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
|