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