facon 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|