mocha 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/COPYING +3 -0
- data/README +148 -0
- data/lib/auto_mocha.rb +1 -0
- data/lib/auto_mocha/auto_mock.rb +54 -0
- data/lib/auto_mocha/mock_class.rb +38 -0
- data/lib/mocha.rb +1 -0
- data/lib/mocha/expectation.rb +116 -0
- data/lib/mocha/infinite_range.rb +27 -0
- data/lib/mocha/inspect.rb +37 -0
- data/lib/mocha/metaclass.rb +7 -0
- data/lib/mocha/mock.rb +30 -0
- data/lib/mocha/mock_methods.rb +55 -0
- data/lib/mocha/pretty_parameters.rb +28 -0
- data/lib/stubba.rb +2 -0
- data/lib/stubba/any_instance_method.rb +31 -0
- data/lib/stubba/class_method.rb +61 -0
- data/lib/stubba/instance_method.rb +22 -0
- data/lib/stubba/object.rb +77 -0
- data/lib/stubba/stubba.rb +27 -0
- data/lib/stubba/test_case.rb +65 -0
- data/test/all_tests.rb +100 -0
- data/test/auto_mocha/auto_mock_test.rb +85 -0
- data/test/auto_mocha/mock_class_test.rb +179 -0
- data/test/auto_mock_acceptance_test.rb +36 -0
- data/test/method_definer.rb +18 -0
- data/test/mocha/expectation_test.rb +216 -0
- data/test/mocha/infinite_range_test.rb +50 -0
- data/test/mocha/inspect_test.rb +79 -0
- data/test/mocha/mock_methods_test.rb +141 -0
- data/test/mocha/mock_test.rb +64 -0
- data/test/mocha/pretty_parameters_test.rb +32 -0
- data/test/mocha_acceptance_test.rb +112 -0
- data/test/stubba/any_instance_method_test.rb +113 -0
- data/test/stubba/class_method_test.rb +149 -0
- data/test/stubba/instance_method_test.rb +97 -0
- data/test/stubba/object_test.rb +147 -0
- data/test/stubba/stubba_test.rb +62 -0
- data/test/stubba/test_case_test.rb +41 -0
- data/test/stubba_acceptance_test.rb +107 -0
- data/test/stubba_integration_test.rb +59 -0
- data/test/stubba_replacer.rb +13 -0
- data/test/test_helper.rb +4 -0
- metadata +91 -0
data/COPYING
ADDED
data/README
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
= Mocha
|
2
|
+
|
3
|
+
Mocha is a library for mocking and stubbing with unit tests using a syntax like that of JMock[http://www.jmock.org] and SchMock[http://rubyforge.org/projects/schmock].
|
4
|
+
|
5
|
+
Mocha comes in three parts:
|
6
|
+
|
7
|
+
1. Mocha - traditional mock objects with expectations and verification
|
8
|
+
2. Stubba - allows mocking and stubbing of methods on real (non-mock) classes
|
9
|
+
3. AutoMocha - magically provides mocks in the place of undefined classes
|
10
|
+
|
11
|
+
Stubba & AutoMocha are the main difference between this mocking library and others like FlexMock[http://onestepback.org/software/flexmock] and RSpec[http://rspec.rubyforge.org].
|
12
|
+
|
13
|
+
== Provenance
|
14
|
+
|
15
|
+
Mocha & Stubba have been created by amalgamating a number of techniques developed by my Reevoo[http://www.reevoo.com] colleagues (Ben[http://www.reevoo.com/blogs/bengriffiths/], Chris[http://blog.seagul.co.uk] & Paul[http://po-ru.com]) and I[http:blog.floehopper.org] under a common syntax. They are both in use on real-world projects using {Ruby On Rails}[http://www.rubyonrails.org]. AutoMocha is more experimental and is at an earlier stage of development.
|
16
|
+
|
17
|
+
== Download & Installation
|
18
|
+
|
19
|
+
You can download Mocha from here[http://rubyforge.org/projects/mocha] or install Mocha with the following command.
|
20
|
+
|
21
|
+
$ gem install flexmock
|
22
|
+
|
23
|
+
== License
|
24
|
+
|
25
|
+
Copyright Revieworld Ltd. 2006
|
26
|
+
|
27
|
+
You may use, copy and redistribute this library under the same terms as Ruby itself (see http://www.ruby-lang.org/en/LICENSE.txt).
|
28
|
+
|
29
|
+
== Simple Mocha Example
|
30
|
+
|
31
|
+
class Enterprise
|
32
|
+
|
33
|
+
def initialize(dilithium)
|
34
|
+
@dilithium = dilithium
|
35
|
+
end
|
36
|
+
|
37
|
+
def go(warp_factor)
|
38
|
+
warp_factor.times { @dilithium.nuke(:anti_matter) }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
require 'mocha'
|
44
|
+
|
45
|
+
class EnterpriseTest < Test::Unit::TestCase
|
46
|
+
|
47
|
+
include Mocha
|
48
|
+
|
49
|
+
def test_should_boldly_go
|
50
|
+
dilithium = Mock.new
|
51
|
+
dilithium.expects(:nuke).with(:anti_matter).at_least_once
|
52
|
+
enterprise = Enterprise.new(dilithium)
|
53
|
+
enterprise.go(2)
|
54
|
+
dilithium.verify
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
== Simple Stubba Example
|
60
|
+
|
61
|
+
class Order
|
62
|
+
|
63
|
+
def total_cost
|
64
|
+
line_items.inject(0) { |total, line_item| total + line_item.price } + shipping_cost
|
65
|
+
end
|
66
|
+
|
67
|
+
def total_weight
|
68
|
+
line_items.inject(0) { |total, line_item| total + line_item.weight }
|
69
|
+
end
|
70
|
+
|
71
|
+
def shipping_cost
|
72
|
+
total_weight * 5 + 10
|
73
|
+
end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
|
77
|
+
def find_all
|
78
|
+
Database.connection.select_all('select * from orders')
|
79
|
+
end
|
80
|
+
|
81
|
+
def number_shipped_since(date)
|
82
|
+
find_all.select { |order| order.shipped_on > date }.size
|
83
|
+
end
|
84
|
+
|
85
|
+
def unshipped_value
|
86
|
+
find_all.inject(0) { |order| order.shipped_on ? 0 : order.total_cost }
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
require 'stubba'
|
93
|
+
|
94
|
+
class OrderTest < Test::Unit::TestCase
|
95
|
+
|
96
|
+
# illustrates stubbing instance method
|
97
|
+
def test_should_calculate_shipping_cost_based_on_total_weight
|
98
|
+
order = Order.new
|
99
|
+
order.stubs(:total_weight).returns(10)
|
100
|
+
assert_equal 60, order.shipping_cost
|
101
|
+
end
|
102
|
+
|
103
|
+
# illustrates stubbing class method
|
104
|
+
def test_should_count_number_of_orders_shipped_after_specified_date
|
105
|
+
order_1 = Order.new(:shipped_on => 1.week.ago)
|
106
|
+
order_2 = Order.new(:shipped_on => 3.weeks.ago)
|
107
|
+
Order.stubs(:find_all).returns([order_1, order_2])
|
108
|
+
assert_equal 1, Order.number_shipped_since(2.weeks.ago)
|
109
|
+
end
|
110
|
+
|
111
|
+
# illustrates stubbing instance method for all instances of a class
|
112
|
+
def test_should_calculate_value_of_unshipped_orders
|
113
|
+
order_1 = Order.create
|
114
|
+
order_2 = Order.create
|
115
|
+
Order.any_instance.stubs(:shipped_on).returns(Time.now)
|
116
|
+
Order.any_instance.stubs(:total_cost).returns(10)
|
117
|
+
assert_equal 20, Order.unshipped_value
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
== Simple AutoMocha Example
|
123
|
+
|
124
|
+
class Article
|
125
|
+
|
126
|
+
def accepted_comments
|
127
|
+
Comment.find_all_by_article_id(self.id).select { |comment| comment.accepted? }
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
require 'auto_mocha'
|
133
|
+
|
134
|
+
class OrderTest < Test::Unit::TestCase
|
135
|
+
|
136
|
+
include Mocha
|
137
|
+
|
138
|
+
# illustrates stubbing of previously undefined class Comment
|
139
|
+
def test_should_return_accepted_comments_for_this_article
|
140
|
+
unaccepted_comment = Mock.new(:accepted? => false)
|
141
|
+
accepted_comment = Mock.new(:accepted? => true)
|
142
|
+
comments = [unaccepted_comment, accepted_comment]
|
143
|
+
Comment.stubs(:find_all_by_article_id).returns(comments)
|
144
|
+
article = Article.new
|
145
|
+
assert_equal [accepted_comment], article.accepted_comments
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
data/lib/auto_mocha.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'auto_mocha/auto_mock'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'auto_mocha/mock_class'
|
2
|
+
|
3
|
+
class Module
|
4
|
+
|
5
|
+
def mochas
|
6
|
+
@@mochas ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def reset_mochas
|
10
|
+
@@mochas = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def const_missing(symbol)
|
14
|
+
mochas[symbol] ||= Mocha::MockClass.dup
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify_all
|
18
|
+
mochas.each_value { |mocha| mocha.verify_all }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
Mocha::MockClass.class_eval do
|
24
|
+
|
25
|
+
class << self
|
26
|
+
|
27
|
+
def mochas
|
28
|
+
@mochas ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def const_missing(symbol)
|
32
|
+
mochas[symbol] ||= Mocha::MockClass.dup
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify_all
|
36
|
+
mochas.each_value { |mocha| mocha.verify }
|
37
|
+
verify
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class Test::Unit::TestCase
|
45
|
+
|
46
|
+
def reset_mochas
|
47
|
+
Object.reset_mochas
|
48
|
+
end
|
49
|
+
|
50
|
+
def verify_all
|
51
|
+
Object.verify_all
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'mocha/mock_methods'
|
2
|
+
|
3
|
+
module Mocha
|
4
|
+
|
5
|
+
class MockClass
|
6
|
+
|
7
|
+
include MockMethods
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
include MockMethods
|
12
|
+
|
13
|
+
def super_method_missing(symbol, *arguments, &block)
|
14
|
+
superclass.method_missing(symbol, *arguments, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :__new__, :new
|
18
|
+
|
19
|
+
def new(*arguments, &block)
|
20
|
+
method_missing(:new, *arguments, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def inherited(subclass)
|
24
|
+
subclass.class_eval do
|
25
|
+
|
26
|
+
def self.new(*arguments, &block)
|
27
|
+
__new__(*arguments, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/mocha.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'mocha/mock'
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'mocha/infinite_range'
|
2
|
+
require 'mocha/pretty_parameters'
|
3
|
+
|
4
|
+
module Mocha
|
5
|
+
class Expectation
|
6
|
+
|
7
|
+
class InvalidExpectation < Exception; end
|
8
|
+
|
9
|
+
class AlwaysEqual
|
10
|
+
def ==(other)
|
11
|
+
true
|
12
|
+
end
|
13
|
+
def to_s
|
14
|
+
"** any **"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :method_name
|
19
|
+
|
20
|
+
def initialize(method_name)
|
21
|
+
@method_name = method_name
|
22
|
+
@count = 1
|
23
|
+
@parameters, @parameter_block = AlwaysEqual.new, nil
|
24
|
+
@invoked, @return_value = 0, nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def match?(method_name, *arguments)
|
28
|
+
if @parameter_block then
|
29
|
+
@parameter_block.call(*arguments)
|
30
|
+
else
|
31
|
+
(@method_name == method_name) and (@parameters == arguments)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def times(range)
|
36
|
+
@count = range
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def never
|
41
|
+
times(0)
|
42
|
+
end
|
43
|
+
|
44
|
+
def at_least(minimum)
|
45
|
+
times(Range.at_least(minimum))
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def at_least_once()
|
50
|
+
at_least(1)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def with(*arguments, ¶meter_block)
|
55
|
+
@parameters, @parameter_block = arguments, parameter_block
|
56
|
+
class << @parameters; def to_s; join(', '); end; end
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def returns(value)
|
61
|
+
@return_value = value
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def raises(exception = RuntimeError, message = nil)
|
66
|
+
@return_value = lambda{ raise exception, message }
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def invoke
|
71
|
+
@invoked += 1
|
72
|
+
@return_value.is_a?(Proc) ? @return_value.call : @return_value
|
73
|
+
end
|
74
|
+
|
75
|
+
def verify
|
76
|
+
unless (@count === @invoked) then
|
77
|
+
raise Test::Unit::AssertionFailedError, "#{message}: expected calls: #{@count}, actual calls: #{@invoked}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def message
|
82
|
+
params = @parameters.is_a?(Array) ? @parameters : [@parameters.to_s]
|
83
|
+
params = PrettyParameters.new(params)
|
84
|
+
":#{@method_name}(#{params.pretty})"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class Stub < Expectation
|
90
|
+
|
91
|
+
def verify
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class MissingExpectation < Expectation
|
98
|
+
|
99
|
+
def initialize(method_name, expectations = [])
|
100
|
+
super(method_name)
|
101
|
+
@expectations = expectations
|
102
|
+
@invoked = true
|
103
|
+
end
|
104
|
+
|
105
|
+
def verify
|
106
|
+
msg = "Unexpected message #{message}"
|
107
|
+
msg << "\nSimilar expectations #{similar_expectations.collect { |expectation| expectation.message }.join("\n") }" unless similar_expectations.empty?
|
108
|
+
raise Test::Unit::AssertionFailedError, msg if @invoked
|
109
|
+
end
|
110
|
+
|
111
|
+
def similar_expectations
|
112
|
+
@expectations.select { |expectation| expectation.method_name == self.method_name }
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Range
|
2
|
+
|
3
|
+
def self.at_least(minimum_value)
|
4
|
+
Range.new(minimum_value, infinite)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.at_most(maximum_value)
|
8
|
+
Range.new(-infinite, maximum_value, false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.infinite
|
12
|
+
1/0.0
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :__to_s__, :to_s
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
if first.to_f.infinite? then
|
19
|
+
return "at most #{last}"
|
20
|
+
elsif last.to_f.infinite? then
|
21
|
+
return "at least #{first}"
|
22
|
+
else
|
23
|
+
__to_s__
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def mocha_inspect
|
5
|
+
inspect =~ /#</ ? "#<#{self.class}: #{self.object_id}>" : inspect
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class String
|
10
|
+
def mocha_inspect
|
11
|
+
inspect.gsub(/\"/, "'")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Array
|
16
|
+
def mocha_inspect
|
17
|
+
"[#{collect { |member| member.mocha_inspect }.join(', ')}]"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Hash
|
22
|
+
def mocha_inspect
|
23
|
+
"{#{collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')}}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Time
|
28
|
+
def mocha_inspect
|
29
|
+
"#{inspect} (#{to_f} secs)"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Date
|
34
|
+
def mocha_inspect
|
35
|
+
to_s
|
36
|
+
end
|
37
|
+
end
|
data/lib/mocha/mock.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'mocha/mock_methods'
|
2
|
+
require 'test/unit/assertions'
|
3
|
+
|
4
|
+
module Mocha
|
5
|
+
class Mock
|
6
|
+
|
7
|
+
include MockMethods
|
8
|
+
|
9
|
+
attr_reader :mocked
|
10
|
+
|
11
|
+
def initialize(*arguments)
|
12
|
+
@mocked = arguments.shift unless arguments.first.is_a?(Hash)
|
13
|
+
@mocked ||= always_responds
|
14
|
+
expectations = arguments.shift || {}
|
15
|
+
expectations.each do |method_name, result|
|
16
|
+
expects(method_name).returns(result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def expects(symbol)
|
21
|
+
raise Test::Unit::AssertionFailedError, "Cannot replace #{symbol} as #{@mocked} does not respond to it." unless @mocked.respond_to?(symbol)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def always_responds
|
26
|
+
Class.new { def respond_to?(symbol); true; end }.new
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|