facon 0.1
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.
- data/History.txt +5 -0
- data/Manifest.txt +17 -0
- data/README.txt +87 -0
- data/Rakefile +16 -0
- data/lib/facon.rb +16 -0
- data/lib/facon/baconize.rb +95 -0
- data/lib/facon/core_ext/object.rb +3 -0
- data/lib/facon/error_generator.rb +42 -0
- data/lib/facon/expectation.rb +130 -0
- data/lib/facon/mock.rb +41 -0
- data/lib/facon/mockable.rb +43 -0
- data/lib/facon/proxy.rb +99 -0
- data/spec/baconize_spec.rb +35 -0
- data/spec/expectation_spec.rb +124 -0
- data/spec/mock_spec.rb +22 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/stub_spec.rb +127 -0
- metadata +80 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
lib/facon.rb
|
6
|
+
lib/facon/baconize.rb
|
7
|
+
lib/facon/core_ext/object.rb
|
8
|
+
lib/facon/error_generator.rb
|
9
|
+
lib/facon/expectation.rb
|
10
|
+
lib/facon/mock.rb
|
11
|
+
lib/facon/mockable.rb
|
12
|
+
lib/facon/proxy.rb
|
13
|
+
spec/baconize_spec.rb
|
14
|
+
spec/expectation_spec.rb
|
15
|
+
spec/mock_spec.rb
|
16
|
+
spec/spec_helper.rb
|
17
|
+
spec/stub_spec.rb
|
data/README.txt
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
= Facon
|
2
|
+
|
3
|
+
Facon is a mocking library in the spirit of the Bacon spec library. Small, compact, and works with Bacon.
|
4
|
+
|
5
|
+
== Synopsis
|
6
|
+
|
7
|
+
To use Facon with Bacon[http://rubyforge.org/projects/test-spec/], simply <code>require 'facon'</code> and you're done.
|
8
|
+
|
9
|
+
You can now write Bacon specs like this (in RSpec-like style):
|
10
|
+
|
11
|
+
require 'bacon'
|
12
|
+
require 'facon'
|
13
|
+
|
14
|
+
describe 'PersonController' do
|
15
|
+
before do
|
16
|
+
@konata = mock('konata', :id => 1, :name => 'Konata Izumi')
|
17
|
+
@kagami = mock('kagami', :id => 2, :name => 'Kagami Hiiragi')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should find all people on GET to 'index'" do
|
21
|
+
Person.should.receive(:find).with(:all).and_return([@konata, @kagami])
|
22
|
+
|
23
|
+
get('/people/index')
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should find the person with id of 1 on Get to 'show/1'" do
|
27
|
+
Person.should.receive(:find).with(1).and_return(@konata)
|
28
|
+
|
29
|
+
get('/people/show/1')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
For now, more examples can be found in the specs included with the Facon gem. I promise to get better examples into the documentation!
|
34
|
+
|
35
|
+
See Facon::Baconize for more documentation on using Facon with Bacon[http://rubyforge.org/projects/test-spec/].
|
36
|
+
|
37
|
+
== Requirements
|
38
|
+
|
39
|
+
* Ruby 1.8
|
40
|
+
* Bacon (optional, required for running specs)
|
41
|
+
|
42
|
+
== Installation
|
43
|
+
|
44
|
+
Simply install the gem:
|
45
|
+
gem install facon
|
46
|
+
|
47
|
+
== Links
|
48
|
+
|
49
|
+
* Facon website - http://facon.rubyforge.org/
|
50
|
+
* Facon Rubyforge project - http://rubyforge.org/projects/facon/
|
51
|
+
* Bacon - http://rubyforge.org/projects/test-spec/
|
52
|
+
* RSpec - http://rspec.info/
|
53
|
+
|
54
|
+
== Todos
|
55
|
+
|
56
|
+
* test/unit and RSpec integration.
|
57
|
+
* Remove the $facon_mocks global.
|
58
|
+
|
59
|
+
== Thanks to
|
60
|
+
|
61
|
+
* RSpec (http://rspec.info/) for creating spec/mocks, from which a lot of the code for Facon is stolen.
|
62
|
+
* Christian Neukirchen (http://rubyforge.org/projects/test-spec/) for creating Bacon.
|
63
|
+
|
64
|
+
== License:
|
65
|
+
|
66
|
+
(The MIT License)
|
67
|
+
|
68
|
+
Copyright (c) 2008 Cheah Chu Yeow
|
69
|
+
|
70
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
71
|
+
a copy of this software and associated documentation files (the
|
72
|
+
'Software'), to deal in the Software without restriction, including
|
73
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
74
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
75
|
+
permit persons to whom the Software is furnished to do so, subject to
|
76
|
+
the following conditions:
|
77
|
+
|
78
|
+
The above copyright notice and this permission notice shall be
|
79
|
+
included in all copies or substantial portions of the Software.
|
80
|
+
|
81
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
82
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
83
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
84
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
85
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
86
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
87
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
4
|
+
require 'facon'
|
5
|
+
|
6
|
+
Hoe.new('facon', Facon::VERSION) do |p|
|
7
|
+
p.rubyforge_name = 'facon'
|
8
|
+
p.author = 'Cheah Chu Yeow'
|
9
|
+
p.email = 'chuyeow@gmail.com'
|
10
|
+
p.summary = 'Tiny mocking library.'
|
11
|
+
p.url = 'http://facon.rubyforge.org/'
|
12
|
+
p.description = 'A mocking library in the spirit of the Bacon spec library. Small, compact, and works with Bacon.'
|
13
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
14
|
+
p.remote_rdoc_dir = 'rdocs'
|
15
|
+
# p.clean_globs = ['test/actual'] # Remove this directory on "rake clean"
|
16
|
+
end
|
data/lib/facon.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'facon/mockable'
|
4
|
+
require 'facon/mock'
|
5
|
+
require 'facon/error_generator'
|
6
|
+
require 'facon/expectation'
|
7
|
+
require 'facon/proxy'
|
8
|
+
require 'facon/baconize'
|
9
|
+
|
10
|
+
require 'facon/core_ext/object'
|
11
|
+
|
12
|
+
# Facon is a mocking library in the spirit of the Bacon spec library. Small,
|
13
|
+
# compact, and works with Bacon.
|
14
|
+
module Facon
|
15
|
+
VERSION = '0.1'
|
16
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Facon
|
2
|
+
# == Bacon integration
|
3
|
+
#
|
4
|
+
# To use Facon with Bacon, simply <code>require 'facon'</code>. Facon injects
|
5
|
+
# itself into Bacon if it can find it, so all you have to do is to make sure
|
6
|
+
# you have Bacon and Facon available on the load path.
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# In <code>spec_helper.rb</code>:
|
11
|
+
# require 'rubygems'
|
12
|
+
# require 'bacon'
|
13
|
+
# require 'facon'
|
14
|
+
#
|
15
|
+
# Simply <code>require</code> your <code>spec_helper.rb</code> in your specs and you are now
|
16
|
+
# able to create mocks and expectations:
|
17
|
+
#
|
18
|
+
# require '/path/to/spec_helper'
|
19
|
+
#
|
20
|
+
# describe 'My examples' do
|
21
|
+
# it "should allow mocks and expectations" do
|
22
|
+
# @mock = mock('test mock')
|
23
|
+
# @mock.should.receive(:message).and_return(:foo)
|
24
|
+
#
|
25
|
+
# do_something_with(@mock)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
module Baconize
|
29
|
+
|
30
|
+
# Mixin intended for Bacon::Context so that it runs spec_verify on all mocks
|
31
|
+
# after each example.
|
32
|
+
module ContextExtensions
|
33
|
+
def self.included(base)
|
34
|
+
base.class_eval do
|
35
|
+
alias_method :it_without_mock_verification, :it
|
36
|
+
alias_method :it, :it_with_mock_verification
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_facon_mocks
|
41
|
+
$facon_mocks ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_facon_mocks
|
45
|
+
$facon_mocks.each { |mock| mock.spec_verify }
|
46
|
+
end
|
47
|
+
|
48
|
+
def it_with_mock_verification(description, &block)
|
49
|
+
setup_facon_mocks
|
50
|
+
it_without_mock_verification(description, &block)
|
51
|
+
verify_facon_mocks
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Mixin intended for Bacon's Should class so that we can do
|
56
|
+
# mock.should.receive(:message) and mock.should.not.receive(:message).
|
57
|
+
module ShouldExtensions
|
58
|
+
def self.included(base)
|
59
|
+
# Remove Facon::Mockable methods we mixed in to Object, since we don't
|
60
|
+
# need those in the Should class.
|
61
|
+
base.class_eval do
|
62
|
+
instance_methods.each do |method|
|
63
|
+
undef_method(method) if Facon::Mockable.public_instance_methods.include?(method)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def receive(method, &block)
|
69
|
+
if @negated
|
70
|
+
@object.mock_proxy.add_negative_expectation(caller(1)[0], method, &block)
|
71
|
+
else
|
72
|
+
@object.mock_proxy.add_expectation(caller(1)[0], method, &block)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def mock_proxy
|
78
|
+
@mock_proxy ||= Proxy.new(@object, Mock === @object ? @object.name : @object.class.name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
begin
|
86
|
+
Bacon::Context.class_eval { include Facon::Baconize::ContextExtensions }
|
87
|
+
Should.class_eval { include Facon::Baconize::ShouldExtensions }
|
88
|
+
rescue NameError
|
89
|
+
require 'rubygems'
|
90
|
+
require 'bacon'
|
91
|
+
Bacon::Context.class_eval { include Facon::Baconize::ContextExtensions }
|
92
|
+
Should.class_eval { include Facon::Baconize::ShouldExtensions }
|
93
|
+
rescue LoadError
|
94
|
+
puts 'Bacon is not available.'
|
95
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Facon
|
2
|
+
# A helper class for generating errors for expectation errors on mocks.
|
3
|
+
class ErrorGenerator
|
4
|
+
def initialize(target, name)
|
5
|
+
@target, @name = target, name
|
6
|
+
end
|
7
|
+
|
8
|
+
def raise_expectation_error(expectation)
|
9
|
+
message = "#{target_name} expected :#{expectation.method} with #{format_args(*expectation.argument_expectation)} #{format_count(expectation.expected_received_count)}, but received it #{format_count(expectation.actual_received_count)}"
|
10
|
+
raise(Facon::MockExpectationError, message)
|
11
|
+
end
|
12
|
+
|
13
|
+
def raise_unexpected_message_error(method, *args)
|
14
|
+
raise(Facon::MockExpectationError, "#{target_name} received unexpected message :#{method} with #{format_args(*args)}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def raise_unexpected_message_args_error(expectation, *args)
|
18
|
+
message = "#{target_name} expected :#{expectation.method} with #{format_args(*expectation.argument_expectation)}, but received it with #{format_args(*args)}"
|
19
|
+
raise(Facon::MockExpectationError, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def raise_block_failed_error(method, exception_message)
|
23
|
+
message = "#{target_name} received :#{method} but passed block failed with: #{exception_message}"
|
24
|
+
raise(Facon::MockExpectationError, message)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def target_name
|
29
|
+
@name ? "Mock '#{@name}'" : @target.inspect
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_args(*args)
|
33
|
+
return '(no args)' if args.empty?
|
34
|
+
return '(any args)' if args == [:any]
|
35
|
+
"(#{args.map { |a| a.inspect }.join(', ')})"
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_count(count)
|
39
|
+
count == 1 ? '1 time' : "#{count} times"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Facon
|
4
|
+
# An Expectation, also know as a mock method, is an expectation that an object
|
5
|
+
# should receive a specific message during the execution of an example.
|
6
|
+
class Expectation
|
7
|
+
extend ::Forwardable
|
8
|
+
def_delegators :@error_generator, :raise_expectation_error, :raise_block_failed_error
|
9
|
+
|
10
|
+
attr_reader :error_generator, :expectation_ordering, :expected_from, :method, :method_block, :expected_received_count, :actual_received_count, :argument_expectation
|
11
|
+
|
12
|
+
def initialize(error_generator, expectation_ordering, expected_from, method, method_block, expected_received_count = 1)
|
13
|
+
@error_generator = error_generator
|
14
|
+
@expectation_ordering = expectation_ordering
|
15
|
+
@expected_from = expected_from
|
16
|
+
@method = method
|
17
|
+
@method_block = method_block
|
18
|
+
@expected_received_count = expected_received_count
|
19
|
+
|
20
|
+
@argument_expectation = :any
|
21
|
+
@exception_to_raise = nil
|
22
|
+
@symbol_to_throw = nil
|
23
|
+
@actual_received_count = 0
|
24
|
+
@args_to_yield = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets up the expected method to return the given value.
|
28
|
+
def and_return(value)
|
29
|
+
raise MockExpectationError, 'Ambiguous return expectation' unless @method_block.nil?
|
30
|
+
|
31
|
+
@return_block = lambda { value }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets up the expected method to yield with the given arguments.
|
35
|
+
def and_yield(*args)
|
36
|
+
@args_to_yield << args
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets up the expected method to raise the given <code>exception</code>
|
41
|
+
# (default: Exception).
|
42
|
+
def and_raise(exception = Exception)
|
43
|
+
@exception_to_raise = exception
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets up the expected method to throw the given <code>symbol</code>.
|
47
|
+
def and_throw(sym)
|
48
|
+
@symbol_to_throw = sym
|
49
|
+
end
|
50
|
+
|
51
|
+
def with(*args, &block)
|
52
|
+
@method_block = block if block
|
53
|
+
@argument_expectation = args
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def invoke(args, block)
|
58
|
+
begin
|
59
|
+
raise @exception_to_raise unless @exception_to_raise.nil?
|
60
|
+
throw @symbol_to_throw unless @symbol_to_throw.nil?
|
61
|
+
|
62
|
+
return_value = if !@method_block.nil?
|
63
|
+
invoke_method_block(args)
|
64
|
+
elsif @args_to_yield.size > 0
|
65
|
+
@args_to_yield.each { |curr_args| block.call(*curr_args) }
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
if @return_block
|
71
|
+
args << block unless block.nil?
|
72
|
+
@return_block.call(*args)
|
73
|
+
else
|
74
|
+
return_value
|
75
|
+
end
|
76
|
+
ensure
|
77
|
+
@actual_received_count += 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns true if this expectation has been met.
|
82
|
+
# TODO at_least and at_most conditions
|
83
|
+
def met?
|
84
|
+
return true if @expected_received_count == :any ||
|
85
|
+
@expected_received_count == @actual_received_count
|
86
|
+
|
87
|
+
raise_expectation_error(self)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns true if the given <code>method</code> and arguments match this
|
91
|
+
# Expectation.
|
92
|
+
def matches(method, args)
|
93
|
+
@method == method && check_arguments(args)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns true if the given <code>method</code> matches this Expectation,
|
97
|
+
# but the given arguments don't.
|
98
|
+
def matches_name_but_not_args(method, args)
|
99
|
+
@method == method && !check_arguments(args)
|
100
|
+
end
|
101
|
+
|
102
|
+
def negative_expectation_for?(method)
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def check_arguments(args)
|
108
|
+
case @argument_expectation
|
109
|
+
when :any then true
|
110
|
+
when args then true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def invoke_method_block(args)
|
115
|
+
@method_block.call(*args)
|
116
|
+
rescue => e
|
117
|
+
raise_block_failed_error(@method, e.message)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class NegativeExpectation < Expectation
|
122
|
+
def initialize(error_generator, expectation_ordering, expected_from, method, method_block, expected_received_count = 0)
|
123
|
+
super(error_generator, expectation_ordering, expected_from, method, method_block, expected_received_count)
|
124
|
+
end
|
125
|
+
|
126
|
+
def negative_expectation_for?(method)
|
127
|
+
@method == method
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/facon/mock.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Facon
|
2
|
+
# Error returned when an expectation on a mock is not satisfied.
|
3
|
+
class MockExpectationError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# A mock object is a 'fake' object on which you can impose simulated behavior.
|
7
|
+
# Defining expectations and stubs on mock objects allows you to specify
|
8
|
+
# object collaboration without needing to actually instantiate those
|
9
|
+
# collaborative objects.
|
10
|
+
#
|
11
|
+
# == Examples
|
12
|
+
#
|
13
|
+
# mock = mock('A name')
|
14
|
+
# mock = mock('Mock person', :name => 'Konata', :sex => 'female')
|
15
|
+
class Mock
|
16
|
+
include Facon::Mockable
|
17
|
+
|
18
|
+
attr_accessor :name
|
19
|
+
|
20
|
+
def initialize(name, stubs = {})
|
21
|
+
@name = name
|
22
|
+
stub_out(stubs)
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method, *args, &block)
|
26
|
+
super(method, *args, &block)
|
27
|
+
rescue NameError
|
28
|
+
# An unexpected method was called on this mock.
|
29
|
+
mock_proxy.raise_unexpected_message_error(method, *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Stubs out all the given stubs.
|
35
|
+
def stub_out(stubs)
|
36
|
+
stubs.each_pair do |method, response|
|
37
|
+
stub!(method).and_return(response)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Facon
|
2
|
+
# A module containing convenient methods for creating mocks, stubs and
|
3
|
+
# expectations.
|
4
|
+
module Mockable
|
5
|
+
|
6
|
+
# Shortcut for creating a Facon::Mock instance.
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# mock = mock('test mock', :foo => 'bar')
|
11
|
+
# mock.foo # => 'bar'
|
12
|
+
def mock(name, stubs = {})
|
13
|
+
Mock.new(name, stubs)
|
14
|
+
end
|
15
|
+
|
16
|
+
def stub!(method)
|
17
|
+
mock_proxy.add_stub(caller(1)[0], method)
|
18
|
+
end
|
19
|
+
|
20
|
+
def should_receive(method, &block)
|
21
|
+
mock_proxy.add_expectation(caller(1)[0], method, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def should_not_receive(method, &block)
|
25
|
+
mock_proxy.add_negative_expectation(caller(1)[0], method, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Verifies that the expectations set on this mock are all met, otherwise
|
29
|
+
# raises a MockExpectationError.
|
30
|
+
def spec_verify
|
31
|
+
mock_proxy.verify
|
32
|
+
end
|
33
|
+
|
34
|
+
def spec_reset
|
35
|
+
mock_proxy.reset
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the mock proxy object.
|
39
|
+
def mock_proxy
|
40
|
+
@mock_proxy ||= Proxy.new(self, Mock === self ? @name : self.class.name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/facon/proxy.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Facon
|
4
|
+
# A proxy for mock objects. Stubs and expectations are set on this proxy.
|
5
|
+
class Proxy
|
6
|
+
extend ::Forwardable
|
7
|
+
def_delegators :@error_generator, :raise_unexpected_message_error, :raise_unexpected_message_args_error
|
8
|
+
|
9
|
+
def initialize(target, name)
|
10
|
+
@target, @name = target, name
|
11
|
+
@expectations = []
|
12
|
+
@stubs = []
|
13
|
+
@error_generator = ErrorGenerator.new(target, name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_stub(expected_from, method)
|
17
|
+
$facon_mocks << (@target) unless $facon_mocks.nil?
|
18
|
+
define_expected_method(method)
|
19
|
+
|
20
|
+
# A stub is really an expectation that can be called any number of times.
|
21
|
+
@stubs.unshift(Expectation.new(@error_generator, @expectation_ordering, expected_from, method, nil, :any))
|
22
|
+
@stubs.first
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_expectation(expected_from, method, &block)
|
26
|
+
$facon_mocks << (@target) unless $facon_mocks.nil?
|
27
|
+
define_expected_method(method)
|
28
|
+
|
29
|
+
@expectations << Expectation.new(@error_generator, @expectation_ordering, expected_from, method, (block_given? ? block : nil), 1)
|
30
|
+
@expectations.last
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_negative_expectation(expected_from, method, &block)
|
34
|
+
$facon_mocks << (@target) unless $facon_mocks.nil?
|
35
|
+
define_expected_method(method)
|
36
|
+
|
37
|
+
@expectations << NegativeExpectation.new(@error_generator, @expectation_ordering, expected_from, method, (block_given? ? block : nil))
|
38
|
+
@expectations.last
|
39
|
+
end
|
40
|
+
|
41
|
+
def message_received(method, *args, &block)
|
42
|
+
if expectation = find_matching_expectation(method, *args)
|
43
|
+
expectation.invoke(args, block)
|
44
|
+
elsif stub = find_matching_method_stub(method, *args)
|
45
|
+
stub.invoke([], block)
|
46
|
+
elsif expectation = find_almost_matching_expectation(method, *args)
|
47
|
+
raise_unexpected_message_args_error(expectation, *args) unless has_negative_expectation?(method)
|
48
|
+
else
|
49
|
+
@target.send(:method_missing, method, *args, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def verify
|
54
|
+
@expectations.each { |expectation| expectation.met? }
|
55
|
+
ensure
|
56
|
+
reset
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset
|
60
|
+
@expectations.clear
|
61
|
+
@stubs.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
# Defines an expected method that simply calls the
|
66
|
+
# <code>message_received</code> method.
|
67
|
+
def define_expected_method(method)
|
68
|
+
metaclass_eval(<<-EOF, __FILE__, __LINE__)
|
69
|
+
def #{method}(*args, &block)
|
70
|
+
mock_proxy.message_received(:#{method}, *args, &block)
|
71
|
+
end
|
72
|
+
EOF
|
73
|
+
end
|
74
|
+
|
75
|
+
def metaclass
|
76
|
+
(class << @target; self; end)
|
77
|
+
end
|
78
|
+
|
79
|
+
def metaclass_eval(str, filename, lineno)
|
80
|
+
metaclass.class_eval(str, filename, lineno)
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_matching_expectation(method, *args)
|
84
|
+
@expectations.find { |expectation| expectation.matches(method, args) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def find_almost_matching_expectation(method, *args)
|
88
|
+
@expectations.find { |expectation| expectation.matches_name_but_not_args(method, args) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_matching_method_stub(method, *args)
|
92
|
+
@stubs.find { |stub| stub.matches(method, args) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_negative_expectation?(method)
|
96
|
+
@expectations.find { |expectation| expectation.negative_expectation_for?(method) }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "Facon::Baconize" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@mock = Facon::Mock.new('test mock')
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should make the #receive instance method available to Should" do
|
10
|
+
Should.public_instance_methods.should.include?('receive')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should allow expectations on mocks with should.receive(:message)" do
|
14
|
+
@mock.should.receive(:message).and_return(:foo)
|
15
|
+
|
16
|
+
@mock.message.should == :foo
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should allow expectations on mocks with should.not.receive(:message)" do
|
20
|
+
@mock.should.not.receive(:message)
|
21
|
+
|
22
|
+
@mock.to_s
|
23
|
+
# FIXME: needs a better test on the actual error raised, but the
|
24
|
+
# instance_eval in it() makes it hard.
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should allow expectations on arbitrary objects with should.receive(:message)" do
|
28
|
+
Object.should.receive(:message).and_return(:foo)
|
29
|
+
@object = Object.new
|
30
|
+
@object.should.receive(:message).and_return(:bar)
|
31
|
+
|
32
|
+
Object.message.should == :foo
|
33
|
+
@object.message.should == :bar
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A mock object" do
|
4
|
+
before do
|
5
|
+
@mock = Facon::Mock.new('test mock')
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
@mock.spec_reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def verify_mock
|
13
|
+
lambda { @mock.spec_verify }.should.not.raise(Facon::MockExpectationError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should pass when not receiving message specified as not to be received" do
|
17
|
+
@mock.should_not_receive(:not_expected)
|
18
|
+
|
19
|
+
verify_mock
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should pass when receiving message specified as not to be received with different args" do
|
23
|
+
@mock.should_not_receive(:message).with(:not_this)
|
24
|
+
@mock.should_receive(:message).with(:but_this)
|
25
|
+
@mock.message(:but_this)
|
26
|
+
|
27
|
+
verify_mock
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise a MockExpectationError when receiving message specified as not to be received" do
|
31
|
+
@mock.should_not_receive(:not_expected)
|
32
|
+
@mock.not_expected
|
33
|
+
|
34
|
+
lambda { @mock.spec_verify }.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' expected :not_expected with (any args) 0 times, but received it 1 time"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should raise a MockExpectationError when receiving message specified as not to be received with arguments" do
|
38
|
+
@mock.should_not_receive(:not_expected).with(:unexpected_arg)
|
39
|
+
@mock.not_expected(:unexpected_arg)
|
40
|
+
|
41
|
+
lambda { @mock.spec_verify }.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' expected :not_expected with (:unexpected_arg) 0 times, but received it 1 time"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should pass when receiving message specified as not to be received, but with wrong arguments" do
|
45
|
+
@mock.should_not_receive(:not_expected).with(:unexpected_arg)
|
46
|
+
@mock.not_expected(:wrong_arg)
|
47
|
+
|
48
|
+
verify_mock
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should raise a MockExpectationError when receiving message specified as to be received is not called" do
|
52
|
+
@mock.should_receive(:expected_message)
|
53
|
+
|
54
|
+
lambda { @mock.spec_verify }.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' expected :expected_message with (any args) 1 time, but received it 0 times"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should raise a MockExpectationError when receiving message specified as to be received, but with wrong arguments" do
|
58
|
+
@mock.should_receive(:expected_message).with(:expected_arg)
|
59
|
+
|
60
|
+
lambda {
|
61
|
+
@mock.expected_message(:unexpected_arg)
|
62
|
+
}.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' expected :expected_message with (:expected_arg), but received it with (:unexpected_arg)"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise a MockExpectationError when receiving an unexpected message" do
|
66
|
+
lambda {
|
67
|
+
@mock.not_expected
|
68
|
+
}.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' received unexpected message :not_expected with (no args)"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should raise a MockExpectationError when receiving an unexpected message with arguments" do
|
72
|
+
lambda {
|
73
|
+
@mock.not_expected(:foo, :bar)
|
74
|
+
}.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' received unexpected message :not_expected with (:foo, :bar)"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should use block for expectation if provided" do
|
78
|
+
@mock.should_receive(:expected_message) do |a, b|
|
79
|
+
a.should == :first
|
80
|
+
b.should == :second
|
81
|
+
'foo'
|
82
|
+
end
|
83
|
+
|
84
|
+
@mock.expected_message(:first, :second).should == 'foo'
|
85
|
+
|
86
|
+
verify_mock
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should use block with given arguments for expectation if provided" do
|
90
|
+
@mock.should_receive(:expected_message).with(:expected_arg) do |arg|
|
91
|
+
arg.should == :expected_arg
|
92
|
+
'foo'
|
93
|
+
end
|
94
|
+
|
95
|
+
@mock.expected_message(:expected_arg).should == 'foo'
|
96
|
+
|
97
|
+
verify_mock
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should raise a MockExpectationError if block for expectation fails" do
|
101
|
+
@mock.should_receive(:expected_message) do |arg|
|
102
|
+
arg.should == :expected_arg
|
103
|
+
end
|
104
|
+
|
105
|
+
lambda {
|
106
|
+
@mock.expected_message(:unexpected_arg)
|
107
|
+
}.should.raise(Facon::MockExpectationError).message.should == "Mock 'test mock' received :expected_message but passed block failed with: :unexpected_arg.==(:expected_arg) failed"
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should raise a MockExpectationError if there's a block for expectation and an #and_return expectation is also set" do
|
111
|
+
lambda {
|
112
|
+
@mock.should_receive(:foo) do
|
113
|
+
:this
|
114
|
+
end.and_return(:that)
|
115
|
+
}.should.raise(Facon::MockExpectationError).message.should == 'Ambiguous return expectation'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
data/spec/mock_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "Facon::Mock" do
|
4
|
+
before do
|
5
|
+
@mock = Facon::Mock.new('test mock')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should stub out a method on a mock" do
|
9
|
+
@mock.stub!(:stubbed_method).and_return(:stub_value)
|
10
|
+
|
11
|
+
@mock.stubbed_method.should == :stub_value
|
12
|
+
@mock.stubbed_method.should == :stub_value # called twice to ensure it stays
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should stub out a method on a non-mock" do
|
16
|
+
non_mock = Object.new
|
17
|
+
non_mock.stub!(:stubbed_method).and_return(:stub_value)
|
18
|
+
|
19
|
+
non_mock.stubbed_method.should == :stub_value
|
20
|
+
non_mock.stubbed_method.should == :stub_value # called twice to ensure it stays
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/stub_spec.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "A method stub" do
|
4
|
+
before do
|
5
|
+
@class = Class.new do
|
6
|
+
def self.existing_class_method
|
7
|
+
:original_value
|
8
|
+
end
|
9
|
+
|
10
|
+
def existing_instance_method
|
11
|
+
:original_value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
@object = @class.new
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should override existing methods" do
|
18
|
+
@object.existing_instance_method.should == :original_value
|
19
|
+
@object.stub!(:existing_instance_method).and_return(:stubbed_value)
|
20
|
+
|
21
|
+
@object.existing_instance_method.should == :stubbed_value
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return the expected value given to #and_return when the stubbed method is received" do
|
25
|
+
@object.stub!(:stubbed_method).and_return(:stubbed_value)
|
26
|
+
|
27
|
+
@object.stubbed_method.should == :stubbed_value
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should ignore when the stubbed method is received" do
|
31
|
+
@object.stub!(:stubbed_method)
|
32
|
+
|
33
|
+
lambda {
|
34
|
+
@object.stubbed_method
|
35
|
+
}.should.not.raise
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should ignore when the stubbed method is received with arguments" do
|
39
|
+
@object.stub!(:stubbed_method)
|
40
|
+
|
41
|
+
lambda {
|
42
|
+
@object.stubbed_method(:some_arg)
|
43
|
+
}.should.not.raise
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should yield the argument given to #and_yield" do
|
47
|
+
@object.stub!(:yielding_method).and_yield(:yielded)
|
48
|
+
|
49
|
+
yielded = nil
|
50
|
+
@object.yielding_method { |arg| yielded = arg }
|
51
|
+
|
52
|
+
yielded.should == :yielded
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should yield the multiple arguments given to #and_yield" do
|
56
|
+
@object.stub!(:yielding_method).and_yield(:first, :second)
|
57
|
+
|
58
|
+
first_arg, second_arg = nil, nil
|
59
|
+
@object.yielding_method { |arg1, arg2| first_arg, second_arg = arg1, arg2 }
|
60
|
+
|
61
|
+
first_arg.should == :first
|
62
|
+
second_arg.should == :second
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should yield multiple times with multiple calls to #and_yield (i.e. allow chaining of #and_yield calls)" do
|
66
|
+
@object.stub!(:yielding_method).and_yield(:one).and_yield(:two)
|
67
|
+
|
68
|
+
yielded = []
|
69
|
+
@object.yielding_method { |arg| yielded << arg }
|
70
|
+
|
71
|
+
yielded.should == [:one, :two]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be able to yield and return different objects (i.e. allow #and_yield and #and_return chaining)" do
|
75
|
+
@object.stub!(:yield_and_return).and_yield(:yielded).and_return(:returned)
|
76
|
+
|
77
|
+
yielded = nil
|
78
|
+
@object.yield_and_return { |arg| yielded = arg }.should == :returned
|
79
|
+
|
80
|
+
yielded.should == :yielded
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should raise the expected exception given to #and_raise" do
|
84
|
+
@object.stub!(:failing_method).and_raise(RuntimeError)
|
85
|
+
|
86
|
+
lambda { @object.failing_method }.should.raise(RuntimeError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should throw the expected thrown value given to #and_throw" do
|
90
|
+
@object.stub!(:pitch).and_throw(:ball)
|
91
|
+
|
92
|
+
lambda { @object.pitch }.should.throw(:ball)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should ignore when the stubbed method is never called" do
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "A method stub with arguments" do
|
100
|
+
|
101
|
+
before do
|
102
|
+
@object = Object.new
|
103
|
+
@object.stub!(:stubbed_method).with(:expected_arg).and_return(:stubbed_value)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should ignore when never called" do
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should ignore when called with expected argument" do
|
110
|
+
@object.stubbed_method(:expected_arg)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should raise a NoMethodError when called with no arguments" do
|
114
|
+
lambda { @object.stubbed_method }.should.raise(NoMethodError)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should raise a NoMethodError when called with some other argument" do
|
118
|
+
lambda { @object.stubbed_method(:something_else) }.should.raise(NoMethodError)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should allow another stub with different arguments" do
|
122
|
+
@object.stub!(:stubbed_method).with(:something_else).and_return(:bar)
|
123
|
+
|
124
|
+
@object.stubbed_method(:expected_arg).should == :stubbed_value
|
125
|
+
@object.stubbed_method(:something_else).should == :bar
|
126
|
+
end
|
127
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: facon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cheah Chu Yeow
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-02-10 00:00:00 +08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.0
|
23
|
+
version:
|
24
|
+
description: A mocking library in the spirit of the Bacon spec library. Small, compact, and works with Bacon.
|
25
|
+
email: chuyeow@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- History.txt
|
32
|
+
- Manifest.txt
|
33
|
+
- README.txt
|
34
|
+
files:
|
35
|
+
- History.txt
|
36
|
+
- Manifest.txt
|
37
|
+
- README.txt
|
38
|
+
- Rakefile
|
39
|
+
- lib/facon.rb
|
40
|
+
- lib/facon/baconize.rb
|
41
|
+
- lib/facon/core_ext/object.rb
|
42
|
+
- lib/facon/error_generator.rb
|
43
|
+
- lib/facon/expectation.rb
|
44
|
+
- lib/facon/mock.rb
|
45
|
+
- lib/facon/mockable.rb
|
46
|
+
- lib/facon/proxy.rb
|
47
|
+
- spec/baconize_spec.rb
|
48
|
+
- spec/expectation_spec.rb
|
49
|
+
- spec/mock_spec.rb
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
- spec/stub_spec.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://facon.rubyforge.org/
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options:
|
56
|
+
- --main
|
57
|
+
- README.txt
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: facon
|
75
|
+
rubygems_version: 1.0.1
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: Tiny mocking library.
|
79
|
+
test_files: []
|
80
|
+
|