mockery 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +106 -0
- data/lib/mockery/call.rb +40 -0
- data/lib/mockery/controller.rb +75 -0
- data/lib/mockery/mock.rb +158 -0
- data/lib/mockery/mock_class_factory.rb +54 -0
- data/lib/mockery/record_control.rb +54 -0
- data/lib/mockery/recorder.rb +48 -0
- data/tests/acc/acc_tests.rb +18 -0
- data/tests/acc/mockery/mockery_test.rb +105 -0
- data/tests/unit/mockery/call_test.rb +92 -0
- data/tests/unit/mockery/controller_test.rb +207 -0
- data/tests/unit/mockery/mock_class_factory_test.rb +207 -0
- data/tests/unit/mockery/mock_test.rb +247 -0
- data/tests/unit/mockery/record_control_test.rb +163 -0
- data/tests/unit/mockery/recorder_test.rb +67 -0
- data/tests/unit/unit_tests.rb +29 -0
- metadata +57 -0
data/Rakefile
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/testtask'
|
7
|
+
|
8
|
+
PKG_VERSION = "0.4.1"
|
9
|
+
|
10
|
+
desc "Default task"
|
11
|
+
task :default => [:unit_tests, :acc_tests]
|
12
|
+
|
13
|
+
desc "Run the unit tests"
|
14
|
+
Rake::TestTask.new do |t|
|
15
|
+
t.name = 'unit_tests'
|
16
|
+
#t.libs << 'tests/unit'
|
17
|
+
#t.libs << 'lib'
|
18
|
+
t.test_files = FileList['tests/unit/unit_tests.rb']
|
19
|
+
t.verbose = true
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Run the acceptance tests"
|
23
|
+
Rake::TestTask.new do |t|
|
24
|
+
t.name = 'acc_tests'
|
25
|
+
#t.libs << 'tests/acc'
|
26
|
+
#t.libs << 'lib'
|
27
|
+
t.test_files = FileList['tests/acc/acc_tests.rb']
|
28
|
+
t.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
SOURCE_FILES = FileList.new do |fl|
|
32
|
+
["lib", "tests"].each do |dir|
|
33
|
+
fl.include "#{dir}/**/*"
|
34
|
+
end
|
35
|
+
fl.include "Rakefile"
|
36
|
+
fl.include "setup.rb"
|
37
|
+
fl.exclude( /\bCVS\b/ )
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Build the Mockery distribution"
|
41
|
+
Rake::PackageTask.new("mockery", PKG_VERSION) do |p|
|
42
|
+
p.need_tar = true
|
43
|
+
p.need_zip = true
|
44
|
+
p.package_dir = '../../tmp/mockery-pkg'
|
45
|
+
p.package_files = SOURCE_FILES
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::RDocTask.new do |rd|
|
49
|
+
rd.rdoc_files.include('lib/**/*.rb')
|
50
|
+
rd.template = 'jamis'
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Create the 'build' directory"
|
54
|
+
task :build_build do
|
55
|
+
Dir.mkdir('build') unless File.directory?('build')
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Build rdoc pages"
|
59
|
+
task :build_rdoc => [:rdoc, :build_build] do
|
60
|
+
File.rename('html', 'build/rdoc')
|
61
|
+
end
|
62
|
+
|
63
|
+
desc "create missing file rdoc blank.html"
|
64
|
+
task :build_blank do
|
65
|
+
if ! File.exist?("build/rdoc/blank.html")
|
66
|
+
File.open('build/rdoc/blank.html', 'w') do |handle|
|
67
|
+
handle.puts('<html/>')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
GEM_FILES = FileList.new do |fl|
|
73
|
+
['lib', 'tests'].each do |dir|
|
74
|
+
fl.include "#{dir}/**/*"
|
75
|
+
end
|
76
|
+
fl.include "Rakefile"
|
77
|
+
fl.exclude( /\bCVS\b/ )
|
78
|
+
end
|
79
|
+
|
80
|
+
gem_spec = Gem::Specification.new do |s|
|
81
|
+
s.autorequire = 'mockery/controller'
|
82
|
+
s.description = <<EOF
|
83
|
+
Mockery dynamically generates mock objects and verifies
|
84
|
+
that they are used as expected.
|
85
|
+
EOF
|
86
|
+
s.email = 'shea@gtsdesign.com'
|
87
|
+
s.files = GEM_FILES
|
88
|
+
s.has_rdoc = false
|
89
|
+
s.homepage = 'http://mockery.rubyforge.org'
|
90
|
+
s.name = 'mockery'
|
91
|
+
s.requirements << 'none'
|
92
|
+
s.rubyforge_project = 'mockery'
|
93
|
+
s.summary = "Dynamic mock objects"
|
94
|
+
s.version = PKG_VERSION
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "Create a .gem file for Mockery"
|
98
|
+
task :gem do
|
99
|
+
end
|
100
|
+
|
101
|
+
Rake::GemPackageTask.new(gem_spec) do |p|
|
102
|
+
p.package_dir = '../../tmp/mockery-pkg'
|
103
|
+
p.need_zip = true
|
104
|
+
p.need_tar = true
|
105
|
+
end
|
106
|
+
|
data/lib/mockery/call.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module Mockery
|
3
|
+
|
4
|
+
class Call
|
5
|
+
|
6
|
+
attr_accessor :args,
|
7
|
+
:caller,
|
8
|
+
:method_name,
|
9
|
+
:return_value
|
10
|
+
|
11
|
+
def digest(caller, args)
|
12
|
+
@caller = caller
|
13
|
+
@method_name = args[0]
|
14
|
+
@args = args[1 .. -1]
|
15
|
+
end
|
16
|
+
|
17
|
+
# equality of Call objects is based on equality of
|
18
|
+
# the attributes, with the exception of @caller (since
|
19
|
+
# the recording is done by a Recorder, not an object
|
20
|
+
# of the target class)
|
21
|
+
def ==(call)
|
22
|
+
return call != nil &&
|
23
|
+
call.class == Mockery::Call &&
|
24
|
+
@method_name == call.method_name &&
|
25
|
+
@args == call.args
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
args_str = (@args == nil) ?
|
30
|
+
'nil' :
|
31
|
+
"[#{@args.join(',')}]"
|
32
|
+
return "<#{self.class.name}:0x#{object_id.abs.to_s(16)}" +
|
33
|
+
" caller=#{@caller}" +
|
34
|
+
" method_name=#{@method_name}" +
|
35
|
+
" args=#{args_str}" +
|
36
|
+
">"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Name: Mockery::Controller (lib/mockery/controller.rb)
|
4
|
+
#
|
5
|
+
# Description:
|
6
|
+
#
|
7
|
+
# The Controller class coordinates a test.
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# Copyright 2005 Gary Shea
|
11
|
+
#
|
12
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
13
|
+
# you may not use this file except in compliance with the License.
|
14
|
+
# You may obtain a copy of the License at
|
15
|
+
#
|
16
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
17
|
+
#
|
18
|
+
# Unless required by applicable law or agreed to in writing, software
|
19
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
20
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
21
|
+
# See the License for the specific language governing permissions and
|
22
|
+
# limitations under the License.
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'mockery/call'
|
26
|
+
require 'mockery/mock'
|
27
|
+
require 'mockery/mock_class_factory'
|
28
|
+
require 'mockery/record_control'
|
29
|
+
require 'mockery/recorder'
|
30
|
+
|
31
|
+
module Mockery
|
32
|
+
|
33
|
+
class Controller
|
34
|
+
|
35
|
+
def initialize(*klasses)
|
36
|
+
@mocks = []
|
37
|
+
klasses.each do |klass|
|
38
|
+
mock = Mock.new(klass)
|
39
|
+
@mocks << mock
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def record(&block)
|
44
|
+
recorders = @mocks.collect { |mock| mock.recorder }
|
45
|
+
ctl = RecordControl.new(recorders)
|
46
|
+
args = [recorders, ctl].flatten[0 ... block.arity.abs]
|
47
|
+
block.call(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def try(&block)
|
51
|
+
objs = @mocks.collect { |mock| mock.mocked_object }
|
52
|
+
block.call(*objs)
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate
|
56
|
+
@mocks.each do |mock|
|
57
|
+
return false if ! mock.validate
|
58
|
+
end
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
def error_report
|
63
|
+
report = ''
|
64
|
+
index = 1
|
65
|
+
@mocks.each do |mock|
|
66
|
+
tmp_index = index
|
67
|
+
index = index + 1
|
68
|
+
report << "mock #{tmp_index} (class: #{mock.class})\n"
|
69
|
+
report << mock.error_report
|
70
|
+
end
|
71
|
+
return report
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/mockery/mock.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Name: Mockery::Mock (lib/mockery/mock.rb)
|
4
|
+
#
|
5
|
+
# Description:
|
6
|
+
#
|
7
|
+
# The Mock class handles configuration, construction,
|
8
|
+
# and validation of a single mock object.
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# Copyright 2005 Gary Shea
|
12
|
+
#
|
13
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
14
|
+
# you may not use this file except in compliance with the License.
|
15
|
+
# You may obtain a copy of the License at
|
16
|
+
#
|
17
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
18
|
+
#
|
19
|
+
# Unless required by applicable law or agreed to in writing, software
|
20
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
21
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
22
|
+
# See the License for the specific language governing permissions and
|
23
|
+
# limitations under the License.
|
24
|
+
#
|
25
|
+
|
26
|
+
require 'mockery/call'
|
27
|
+
require 'mockery/mock_class_factory'
|
28
|
+
require 'mockery/recorder'
|
29
|
+
|
30
|
+
module Mockery
|
31
|
+
|
32
|
+
class Mock
|
33
|
+
|
34
|
+
attr_reader :klass, :recorder
|
35
|
+
|
36
|
+
def initialize(klass)
|
37
|
+
@klass = klass
|
38
|
+
@recorder = Recorder.new(klass)
|
39
|
+
@class_factory = MockClassFactory.new(klass)
|
40
|
+
@return_values = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_history
|
44
|
+
@recorder.history.each do |call|
|
45
|
+
method = call.method_name
|
46
|
+
if @return_values.has_key?(method)
|
47
|
+
@return_values[method] << call.return_value
|
48
|
+
else
|
49
|
+
@return_values[method] = [call.return_value]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_methods
|
55
|
+
@return_values.each_key do |method_name|
|
56
|
+
@class_factory.add_method(method_name)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def mocked_object
|
61
|
+
@return_values = Hash.new
|
62
|
+
process_history
|
63
|
+
add_methods
|
64
|
+
@class_factory.create_initialize(@return_values.keys)
|
65
|
+
@instance = @class_factory.create_instance(@return_values)
|
66
|
+
return @instance
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate
|
70
|
+
return @recorder.history == @instance.instance_history
|
71
|
+
end
|
72
|
+
|
73
|
+
def error_report
|
74
|
+
|
75
|
+
report = ''
|
76
|
+
|
77
|
+
recorded = @recorder.history
|
78
|
+
actual = @instance.instance_history
|
79
|
+
if recorded.size > actual.size
|
80
|
+
max_idx = @recorder.history.size
|
81
|
+
else
|
82
|
+
max_idx = @instance.instance_history.size
|
83
|
+
end
|
84
|
+
|
85
|
+
(0 ... max_idx).each do |idx|
|
86
|
+
if idx >= recorded.size
|
87
|
+
report << "unexpected: #{describe_call(actual[idx])}\n"
|
88
|
+
elsif idx >= actual.size
|
89
|
+
report << "expected: #{describe_call(recorded[idx])}\n"
|
90
|
+
elsif recorded[idx] != actual[idx]
|
91
|
+
report << "expected\n#{describe_call(recorded[idx])}\n"
|
92
|
+
report << "but got\n#{describe_call(actual[idx])}\n"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
return report
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def describe_call(call)
|
101
|
+
return '<' + call.caller.class.name + '>.' +
|
102
|
+
call.method_name.to_s + '(' +
|
103
|
+
call.args[0 .. -1].collect{|arg| expanding_describe_value(arg)}.join(', ') +
|
104
|
+
')'
|
105
|
+
end
|
106
|
+
|
107
|
+
def expanding_describe_value(value)
|
108
|
+
dscr = describe_simple_value(value)
|
109
|
+
if dscr == nil
|
110
|
+
dscr = "<#{value.class.name}:#{object_id_to_s(value)} "
|
111
|
+
dscr << value.instance_variables.collect { |var_name|
|
112
|
+
var_dscr = ''
|
113
|
+
var_dscr << "#{var_name}="
|
114
|
+
var_value = value.instance_variable_get(eval(":#{var_name}"))
|
115
|
+
var_dscr << non_expanding_describe_value(var_value)
|
116
|
+
var_dscr
|
117
|
+
}.join(', ')
|
118
|
+
dscr << '>'
|
119
|
+
end
|
120
|
+
return dscr
|
121
|
+
end
|
122
|
+
|
123
|
+
def non_expanding_describe_value(value)
|
124
|
+
dscr = describe_simple_value(value)
|
125
|
+
if dscr == nil
|
126
|
+
dscr = '<' + value.class.name + object_id_to_s(value) + '>'
|
127
|
+
end
|
128
|
+
return dscr
|
129
|
+
end
|
130
|
+
|
131
|
+
def describe_simple_value(value)
|
132
|
+
if value == nil
|
133
|
+
return 'nil'
|
134
|
+
elsif value.class == Fixnum
|
135
|
+
return value.to_s
|
136
|
+
elsif value.class == Symbol
|
137
|
+
return ':' + value.to_s
|
138
|
+
elsif value.class == String
|
139
|
+
return "'" + value + "'"
|
140
|
+
elsif value.class == Array
|
141
|
+
return '[' + value.join(', ') + ']'
|
142
|
+
elsif value.class == Hash
|
143
|
+
dscrs = []
|
144
|
+
value.each { |key, value|
|
145
|
+
dscrs << "#{key} => #{value}"
|
146
|
+
}
|
147
|
+
return '{' + dscrs.join(',') + '}'
|
148
|
+
else
|
149
|
+
return nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def object_id_to_s(obj)
|
154
|
+
return ':0x' + obj.object_id.abs.to_s(16)
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
module Mockery
|
3
|
+
|
4
|
+
class MockClassFactory
|
5
|
+
|
6
|
+
def initialize(user_klass)
|
7
|
+
|
8
|
+
klass = Class.new(user_klass)
|
9
|
+
klass.class_eval {
|
10
|
+
attr_reader :instance_history
|
11
|
+
}
|
12
|
+
|
13
|
+
@klass = klass
|
14
|
+
@return_values = Hash.new
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_method(method_name)
|
19
|
+
var_name = "@___#{method_name}_rv"
|
20
|
+
def_string = "@klass.class_eval {\n" +
|
21
|
+
" def #{method_name}(*args)\n" +
|
22
|
+
" call = Mockery::Call.new\n" +
|
23
|
+
" call.caller = self\n" +
|
24
|
+
" call.method_name = :#{method_name}\n" +
|
25
|
+
" call.args = args\n" +
|
26
|
+
" @instance_history << call\n" +
|
27
|
+
" return #{var_name}.size == 0 ? nil : #{var_name}.shift\n" +
|
28
|
+
" end\n" +
|
29
|
+
"}\n"
|
30
|
+
eval(def_string)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_initialize(method_names)
|
34
|
+
def_string = "@klass.class_eval {\n" +
|
35
|
+
" def initialize(*args, &block)\n" +
|
36
|
+
" @instance_history = []\n"
|
37
|
+
method_names.each do |method, values|
|
38
|
+
def_string << " @__#{method.to_s}_rv = nil\n"
|
39
|
+
end
|
40
|
+
def_string << (" end\n" + "}\n")
|
41
|
+
eval(def_string)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_instance(return_values)
|
45
|
+
mock = @klass.new
|
46
|
+
return_values.each do |method, value|
|
47
|
+
mock.instance_variable_set(eval(":@___#{method}_rv"), value)
|
48
|
+
end
|
49
|
+
return mock
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Name: Mockery::RecordControl (lib/mockery/record_control.rb)
|
4
|
+
#
|
5
|
+
# Description:
|
6
|
+
#
|
7
|
+
# The RecordControl class enables recording to be 'paused'
|
8
|
+
# inside a 'try' block.
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# Copyright 2005 Gary Shea
|
12
|
+
#
|
13
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
14
|
+
# you may not use this file except in compliance with the License.
|
15
|
+
# You may obtain a copy of the License at
|
16
|
+
#
|
17
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
18
|
+
#
|
19
|
+
# Unless required by applicable law or agreed to in writing, software
|
20
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
21
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
22
|
+
# See the License for the specific language governing permissions and
|
23
|
+
# limitations under the License.
|
24
|
+
#
|
25
|
+
|
26
|
+
module Mockery
|
27
|
+
|
28
|
+
class RecordControl
|
29
|
+
|
30
|
+
def initialize(mocks)
|
31
|
+
@mocks = mocks
|
32
|
+
end
|
33
|
+
|
34
|
+
def pause(&block)
|
35
|
+
self.stop
|
36
|
+
block.call
|
37
|
+
self.start
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
@mocks.each do |mock|
|
42
|
+
mock.__record_state = false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def start
|
47
|
+
@mocks.each do |mock|
|
48
|
+
mock.__record_state = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|