xspec 0.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.
- checksums.yaml +7 -0
- data/README.md +141 -0
- data/bin/xspec +24 -0
- data/lib/xspec.rb +61 -0
- data/lib/xspec/assertion_contexts.rb +360 -0
- data/lib/xspec/autorun.rb +8 -0
- data/lib/xspec/data_structures.rb +181 -0
- data/lib/xspec/defaults.rb +30 -0
- data/lib/xspec/dsl.rb +26 -0
- data/lib/xspec/evaluators.rb +40 -0
- data/lib/xspec/notifiers.rb +310 -0
- data/spec/all_specs.rb +9 -0
- data/spec/integration/rspec_expectations_spec.rb +21 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/unit/assertion_spec.rb +67 -0
- data/spec/unit/doubles_spec.rb +199 -0
- data/spec/unit/let_spec.rb +46 -0
- data/spec/unit/notifiers_spec.rb +189 -0
- data/spec/unit/skeleton_spec.rb +7 -0
- data/xspec.gemspec +27 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c981df6d76e0ee48d3cea0ebbe598d88e173c6ce
|
4
|
+
data.tar.gz: 632dd6c9e7230c07d1edd1d4eac07127ae807b8b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1c344141ce89c73cb27b7a7f365368f3dc79329f2d06e404397033a277bbff0fd7d8608d5d4f30e31011dc3cbface6917e7c4bf9e17d8f7d915d5700561455f8
|
7
|
+
data.tar.gz: 2dd80e45854d14ea0ff1247469d27fda60fc73d0e07b8db7c60e2461d483496cb28f7d06d386a968cddfc673efba5a78e02e2471b7fc33a6152df75cfe05b1ee
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
XSpec
|
2
|
+
=====
|
3
|
+
|
4
|
+
XSpec is an rspec-inspired testing library that is written in a literate style
|
5
|
+
and designed to be obvious to use, highly modular, and easy to extend.
|
6
|
+
|
7
|
+
Open up `lib/xspec.rb` and start reading, or use this [nicely formatted
|
8
|
+
version](http://xaviershay.github.io/xspec/).
|
9
|
+
|
10
|
+
Usage
|
11
|
+
-----
|
12
|
+
|
13
|
+
The default configuration XSpec provides a number of interesting features:
|
14
|
+
assertions, doubles, and rich output.
|
15
|
+
|
16
|
+
``` ruby
|
17
|
+
require 'xspec'
|
18
|
+
|
19
|
+
extend XSpec.dsl # Use defaults
|
20
|
+
|
21
|
+
describe 'my application' do
|
22
|
+
it 'does math' do
|
23
|
+
double = instance_double('Calculator')
|
24
|
+
expect(double).add(1, 1) { 2 }
|
25
|
+
|
26
|
+
assert_equal 2, double.add(1, 1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is slow sometimes' do
|
30
|
+
sleep 0.01
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'fails' do
|
34
|
+
assert_include "fruit", "punch"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
Running this with the built-in runner generates some pretty output. You can't
|
40
|
+
see the colors in this README, but trust me they are quite lovely.
|
41
|
+
|
42
|
+
```
|
43
|
+
> bin/xspec example.rb
|
44
|
+
|
45
|
+
my application
|
46
|
+
0.000s does math
|
47
|
+
0.011s is slow sometimes
|
48
|
+
0.000s fails - FAILED
|
49
|
+
|
50
|
+
Timings:
|
51
|
+
0.001 #################### 2
|
52
|
+
0.005 0
|
53
|
+
0.01 0
|
54
|
+
0.1 ########## 1
|
55
|
+
|
56
|
+
|
57
|
+
my application fails:
|
58
|
+
"fruit" not present in: "punch"
|
59
|
+
|
60
|
+
example.rb:18:in `block (2 levels) in <top (required)>'
|
61
|
+
bin/xspec:19:in `<main>'
|
62
|
+
```
|
63
|
+
|
64
|
+
Customization
|
65
|
+
-------------
|
66
|
+
|
67
|
+
Every aspect of XSpec is customizable, from how tests are scheduled and run all
|
68
|
+
the way through to formatting of output.
|
69
|
+
|
70
|
+
Say you wanted boring output with no support for doubles and RSpec
|
71
|
+
expectations. You could do that:
|
72
|
+
|
73
|
+
``` ruby
|
74
|
+
require 'xspec'
|
75
|
+
|
76
|
+
extend XSpec.dsl(
|
77
|
+
assertion_context: XSpec::AssertionContext.stack {
|
78
|
+
include XSpec::AssertionContext::RSpecExpectations
|
79
|
+
},
|
80
|
+
notifier: XSpec::Notifiers::Character.new +
|
81
|
+
XSpec::Notifiers::FailuresAtEnd.new
|
82
|
+
)
|
83
|
+
|
84
|
+
describe '...' do
|
85
|
+
# etc etc
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
Of course, you can easily make your own extension classes as well. A runner
|
90
|
+
that randomly drops tests and reports random durations? Whatever floats your
|
91
|
+
boat:
|
92
|
+
|
93
|
+
``` ruby
|
94
|
+
require 'xspec'
|
95
|
+
|
96
|
+
class UnhelpfulRunner
|
97
|
+
attr_reader :notifier
|
98
|
+
|
99
|
+
def initialize(opts)
|
100
|
+
@notifier = opts.fetch(:notifier)
|
101
|
+
end
|
102
|
+
|
103
|
+
def run(context)
|
104
|
+
notifier.run_start
|
105
|
+
|
106
|
+
context.nested_units_of_work.each do |x|
|
107
|
+
next if rand > 0.9
|
108
|
+
|
109
|
+
notifier.evaluate_start(x)
|
110
|
+
|
111
|
+
errors = x.immediate_parent.execute(x)
|
112
|
+
duration = rand
|
113
|
+
result = XSpec::ExecutedUnitOfWork.new(x, errors, duration)
|
114
|
+
|
115
|
+
notifier.evaluate_finish(result)
|
116
|
+
end
|
117
|
+
|
118
|
+
notifier.run_finish
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
extend XSpec.dsl(
|
123
|
+
evaluator: UnhelpfulRunner.new(notifier: XSpec::Notifier::DEFAULT)
|
124
|
+
)
|
125
|
+
|
126
|
+
describe '...' do
|
127
|
+
# etc etc
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
Developing
|
132
|
+
----------
|
133
|
+
|
134
|
+
Follow the idioms you find in the source, they are somewhat different than
|
135
|
+
a traditional Ruby project. Bug fixes welcome, features likely to be rejected
|
136
|
+
since I have a strong opinion of what this library should and should not do.
|
137
|
+
Talk to me before embarking on anything large. Tests are written in XSpec,
|
138
|
+
which might do your head in:
|
139
|
+
|
140
|
+
bundle install
|
141
|
+
bundle exec bin/run-specs
|
data/bin/xspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift "spec"
|
4
|
+
$LOAD_PATH.unshift "lib"
|
5
|
+
|
6
|
+
files = if ARGV.any? {|x| x.length > 0 }
|
7
|
+
ARGV
|
8
|
+
else
|
9
|
+
Dir['spec/**/*_spec.rb']
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'xspec'
|
13
|
+
|
14
|
+
files.each do |f|
|
15
|
+
load f
|
16
|
+
end
|
17
|
+
|
18
|
+
if respond_to?(:run!)
|
19
|
+
exit 1 unless run!
|
20
|
+
else
|
21
|
+
$stderr.puts "This script can only be used when XSpec.dsl is mixed in to " +
|
22
|
+
"global scope."
|
23
|
+
exit 1
|
24
|
+
end
|
data/lib/xspec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# # XSpec
|
2
|
+
|
3
|
+
# Hello and welcome to XSpec!
|
4
|
+
#
|
5
|
+
# XSpec is an rspec-inspired testing library that is designed to be highly
|
6
|
+
# modular and easy to extend. Let's dive right in.
|
7
|
+
module XSpec
|
8
|
+
# The DSL is the core of XSpec. It dynamically generates a module that can be
|
9
|
+
# mixed into which ever context you choose (using `extend`), be that the
|
10
|
+
# top-level namespace or a specific class.
|
11
|
+
#
|
12
|
+
# This enables different options to be specified per DSL, which is at the
|
13
|
+
# heart of XSpec's modularity. It is easy to change every component to your
|
14
|
+
# liking.
|
15
|
+
def dsl(options = {})
|
16
|
+
options = XSpec.add_defaults(options)
|
17
|
+
|
18
|
+
Module.new do
|
19
|
+
include DSL
|
20
|
+
|
21
|
+
# Each DSL has its own independent context, which is described in detail
|
22
|
+
# in `data_structures.rb`.
|
23
|
+
def __xspec_context
|
24
|
+
assertion_context = __xspec_opts.fetch(:assertion_context)
|
25
|
+
@__xspec_context ||= XSpec::Context.root(assertion_context)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Some meta-magic is needed to enable the options from local scope above
|
29
|
+
# to be available inside the module.
|
30
|
+
define_method(:__xspec_opts) { options }
|
31
|
+
|
32
|
+
# `run!` is where the magic happens. Typically called at the end of a
|
33
|
+
# file (or by `autorun!`), this method takes all the data that was
|
34
|
+
# accumulated by the DSL methods above and runs it through the evaluator.
|
35
|
+
def run!
|
36
|
+
__xspec_opts.fetch(:evaluator).run(__xspec_context)
|
37
|
+
end
|
38
|
+
|
39
|
+
# It is often convenient to trigger a run after all files have been
|
40
|
+
# processed, which is what `autorun!` sets up for you. Requiring
|
41
|
+
# `xspec/autorun` does this automatically for you.
|
42
|
+
def autorun!
|
43
|
+
at_exit do
|
44
|
+
exit 1 unless run!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
module_function :dsl
|
50
|
+
end
|
51
|
+
|
52
|
+
# Understanding the data structures used by XSpec will assist you in
|
53
|
+
# understanding the behavoural components such as the evaluator and notifier.
|
54
|
+
require 'xspec/data_structures'
|
55
|
+
|
56
|
+
# To further explore the code base, dive into the defaults file, which
|
57
|
+
# describes the different sub-components of XSpec that you can use or
|
58
|
+
# customize.
|
59
|
+
require 'xspec/defaults'
|
60
|
+
|
61
|
+
require 'xspec/dsl'
|
@@ -0,0 +1,360 @@
|
|
1
|
+
# # Assertion Contexts
|
2
|
+
|
3
|
+
# Assertion contexts are composed together into a context stack. The final
|
4
|
+
# stack has a single API method `call`, which is sent the unit of work to be
|
5
|
+
# executed and must return an array of `Failure` objects. It should not allow
|
6
|
+
# code-level exceptions to be raised, though should not block system exceptions
|
7
|
+
# (`SignalException`, `NoMemoryError`, etc).
|
8
|
+
module XSpec
|
9
|
+
module AssertionContext
|
10
|
+
# A stack is typically book-ended by the top and bottom contexts, so this
|
11
|
+
# helper is the most commond way to build up a custom stack.
|
12
|
+
def self.stack(&block)
|
13
|
+
Module.new do
|
14
|
+
include Bottom
|
15
|
+
instance_exec &block
|
16
|
+
include Top
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# The bottom context executes the unit of work, with no error handling or
|
21
|
+
# extra behaviour. By separating this, all other contexts layered on top of
|
22
|
+
# this one can just call `super`, making them easy to compose.
|
23
|
+
module Bottom
|
24
|
+
def call(unit_of_work)
|
25
|
+
instance_exec(&unit_of_work.block)
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# The top should be included as the final module in a context stack. It is
|
31
|
+
# a catch all to make sure all standard exceptions have been handled and
|
32
|
+
# do not leak outside the stack.
|
33
|
+
module Top
|
34
|
+
def call(unit_of_work)
|
35
|
+
super
|
36
|
+
rescue => e
|
37
|
+
[CodeException.new(unit_of_work, e.message, e.backtrace)]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# ### Simple Assertions
|
42
|
+
#
|
43
|
+
# This simple context provides very straight-forward assertion methods.
|
44
|
+
module Simple
|
45
|
+
class AssertionFailed < RuntimeError
|
46
|
+
attr_reader :message, :backtrace
|
47
|
+
|
48
|
+
def initialize(message, backtrace)
|
49
|
+
@message = message
|
50
|
+
@backtrace = backtrace
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def call(unit_of_work)
|
55
|
+
super
|
56
|
+
rescue AssertionFailed => e
|
57
|
+
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert(proposition, message=nil)
|
61
|
+
unless proposition
|
62
|
+
message ||= 'assertion failed'
|
63
|
+
|
64
|
+
_raise message
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_equal(expected, actual)
|
69
|
+
unless expected == actual
|
70
|
+
message ||= <<-EOS.chomp
|
71
|
+
want: #{expected.inspect}
|
72
|
+
got: #{actual.inspect}
|
73
|
+
EOS
|
74
|
+
|
75
|
+
_raise message
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_include(expected, output)
|
80
|
+
assert output.include?(expected),
|
81
|
+
"#{expected.inspect} not present in: #{output.inspect}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def fail(message = nil)
|
85
|
+
message ||= 'failed'
|
86
|
+
|
87
|
+
_raise message
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def _raise(message)
|
93
|
+
raise AssertionFailed.new(message, caller)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# ### Doubles
|
98
|
+
#
|
99
|
+
# The doubles module provides test doubles that can be used in-place of
|
100
|
+
# real objects.
|
101
|
+
module Doubles
|
102
|
+
DoubleFailure = Class.new(RuntimeError)
|
103
|
+
|
104
|
+
def call(unit_of_work)
|
105
|
+
super
|
106
|
+
rescue DoubleFailure => e
|
107
|
+
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
108
|
+
end
|
109
|
+
|
110
|
+
# It can be configured with a few options:
|
111
|
+
#
|
112
|
+
# * `auto_verify` calls `assert_exhausted` on all created doubles after a
|
113
|
+
# unit of work executes successfully to ensure that all expectations that
|
114
|
+
# were set were actually called.
|
115
|
+
# * `strict` forbids doubling of classes that have not been loaded. This
|
116
|
+
# should generally be enabled when doing a full spec run, and disabled
|
117
|
+
# when running specs in isolation.
|
118
|
+
#
|
119
|
+
# The `with` method returns a module that can be included in a stack.
|
120
|
+
def self.with(*opts)
|
121
|
+
modules = [self] + opts.map {|x| {
|
122
|
+
auto_verify: AutoVerify,
|
123
|
+
strict: Strict
|
124
|
+
}.fetch(x) }
|
125
|
+
|
126
|
+
|
127
|
+
Module.new do
|
128
|
+
modules.each do |m|
|
129
|
+
include m
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# An instance double stands in for an instance of the given class
|
135
|
+
# reference, given as a string. The class does not need to be loaded, but
|
136
|
+
# if it is then only public instance methods defined on the class are
|
137
|
+
# able to be expected.
|
138
|
+
def instance_double(klass)
|
139
|
+
_double(klass, InstanceReference)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Simarly, a class double validates that class responds to all expected
|
143
|
+
# methods, if that class has been loaded.
|
144
|
+
def class_double(klass)
|
145
|
+
_double(klass, ClassReference)
|
146
|
+
end
|
147
|
+
|
148
|
+
# If the doubled class has not been loaded, a null object reference is
|
149
|
+
# used that allows expecting of all methods.
|
150
|
+
def _double(klass, type)
|
151
|
+
ref = if self.class.const_defined?(klass)
|
152
|
+
type.new(self.class.const_get(klass))
|
153
|
+
else
|
154
|
+
StringReference.new(klass)
|
155
|
+
end
|
156
|
+
|
157
|
+
Double.new(ref)
|
158
|
+
end
|
159
|
+
|
160
|
+
# To set up an expectation on a double, call the expected method an
|
161
|
+
# arguments on the proxy object returned by `expect`. If a return value
|
162
|
+
# is desired, it can be supplied as a block, for example:
|
163
|
+
# `expect(double).some_method(1, 2) { "return value" }`
|
164
|
+
def expect(obj)
|
165
|
+
Recorder.new(obj)
|
166
|
+
end
|
167
|
+
|
168
|
+
class Recorder
|
169
|
+
def initialize(double)
|
170
|
+
@double = double
|
171
|
+
end
|
172
|
+
|
173
|
+
def method_missing(*args, &ret)
|
174
|
+
@double._expect(args, &(ret || ->{}))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Since the double object inherits from `BasicObject`, virtually every
|
179
|
+
# method call will be routed through `method_missing`. From there, the
|
180
|
+
# call can be checked against the expectations that were setup at the
|
181
|
+
# beginning of a spec.
|
182
|
+
class Double < BasicObject
|
183
|
+
def initialize(klass)
|
184
|
+
@klass = klass
|
185
|
+
@expected = []
|
186
|
+
end
|
187
|
+
|
188
|
+
def method_missing(*actual_args)
|
189
|
+
i = @expected.find_index {|expected_args, ret|
|
190
|
+
expected_args == actual_args
|
191
|
+
}
|
192
|
+
|
193
|
+
if i
|
194
|
+
@expected.delete_at(i)[1].call
|
195
|
+
else
|
196
|
+
name, rest = *actual_args
|
197
|
+
::Kernel.raise DoubleFailure, "Unexpectedly received: %s(%s)" % [
|
198
|
+
name,
|
199
|
+
[*rest].map(&:inspect).join(", ")
|
200
|
+
]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# The two methods needed on this object to set it up and verify it are
|
205
|
+
# prefixed by `_` to try to ensure they don't clash with any method
|
206
|
+
# expectations. While not fail-safe, users should only be using
|
207
|
+
# expectations for a public API, and `_` is traditionally only used
|
208
|
+
# for private methods (if at all).
|
209
|
+
def _expect(args, &ret)
|
210
|
+
@klass.validate_call! args
|
211
|
+
|
212
|
+
@expected << [args, ret]
|
213
|
+
end
|
214
|
+
|
215
|
+
def _verify
|
216
|
+
return if @expected.empty?
|
217
|
+
|
218
|
+
::Kernel.raise DoubleFailure, "%s double did not receive:\n%s" % [
|
219
|
+
@klass.to_s,
|
220
|
+
@expected.map {|(name, *args), _|
|
221
|
+
" %s(%s)" % [name, args.map(&:inspect).join(", ")]
|
222
|
+
}.join("\n")
|
223
|
+
]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# A reference can be thought of as a "backing object" for a double. It
|
228
|
+
# provides an API to validate that a method being expected actually
|
229
|
+
# exists - the implementation is different for the different types of
|
230
|
+
# doubles.
|
231
|
+
class Reference
|
232
|
+
def initialize(klass)
|
233
|
+
@klass = klass
|
234
|
+
end
|
235
|
+
|
236
|
+
def validate_call!(args)
|
237
|
+
end
|
238
|
+
|
239
|
+
def to_s
|
240
|
+
@klass.to_s
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# A string reference is the "null object" of references, allowing all
|
245
|
+
# methods to be expected. It is used when nothing is known about the
|
246
|
+
# referenced class (such as when it has not been loaded).
|
247
|
+
class StringReference < Reference
|
248
|
+
end
|
249
|
+
|
250
|
+
# Class and Instance references are backed by loaded classes, and
|
251
|
+
# restrict the messages that can be expected on a double.
|
252
|
+
class ClassReference < Reference
|
253
|
+
def validate_call!(args)
|
254
|
+
name, rest = *args
|
255
|
+
|
256
|
+
unless @klass.respond_to?(name)
|
257
|
+
raise DoubleFailure,
|
258
|
+
"#{@klass}.#{name} is unimplemented or not public"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class InstanceReference < Reference
|
264
|
+
def validate_call!(args)
|
265
|
+
name, rest = *args
|
266
|
+
|
267
|
+
unless @klass.public_instance_methods.include?(name)
|
268
|
+
raise DoubleFailure,
|
269
|
+
"#{@klass}##{name} is unimplemented or not public"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# The `:strict` option mixes in this `Strict` module, which raises rather
|
275
|
+
# than create `StringReference`s for unknown classes.
|
276
|
+
module Strict
|
277
|
+
def _double(klass, type)
|
278
|
+
ref = if self.class.const_defined?(klass)
|
279
|
+
type.new(self.class.const_get(klass))
|
280
|
+
else
|
281
|
+
raise DoubleFailure, "#{klass} is not a valid class name"
|
282
|
+
end
|
283
|
+
|
284
|
+
super
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# An assertion is provided to validate that all expected methods were
|
289
|
+
# called on a double.
|
290
|
+
def assert_exhausted(obj)
|
291
|
+
obj._verify
|
292
|
+
end
|
293
|
+
|
294
|
+
# Most of the time, `assert_exhausted` will not be called directly, since
|
295
|
+
# the `:auto_verify` option can be used to call it by default on all
|
296
|
+
# doubles. That option mixes in this `AutoVerify` module to augment
|
297
|
+
# methods necessary for this behaviour.
|
298
|
+
module AutoVerify
|
299
|
+
def initialize
|
300
|
+
@doubles = []
|
301
|
+
end
|
302
|
+
|
303
|
+
def call(unit_of_work)
|
304
|
+
result = super
|
305
|
+
|
306
|
+
if result.empty?
|
307
|
+
@doubles.each do |double|
|
308
|
+
assert_exhausted double
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
result
|
313
|
+
rescue DoubleFailure => e
|
314
|
+
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
315
|
+
end
|
316
|
+
|
317
|
+
def class_double(klass)
|
318
|
+
x = super
|
319
|
+
@doubles << x
|
320
|
+
x
|
321
|
+
end
|
322
|
+
|
323
|
+
def instance_double(klass)
|
324
|
+
x = super
|
325
|
+
@doubles << x
|
326
|
+
x
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# ### RSpec Integration
|
332
|
+
#
|
333
|
+
# This RSpec adapter shows two useful techniques: dynamic library loading
|
334
|
+
# which removes RSpec as a direct dependency, and use of the `mixin`
|
335
|
+
# method to further extend the target context.
|
336
|
+
module RSpecExpectations
|
337
|
+
def self.included(context)
|
338
|
+
begin
|
339
|
+
require 'rspec/expectations'
|
340
|
+
require 'rspec/matchers'
|
341
|
+
rescue LoadError
|
342
|
+
raise "RSpec is not available, cannot use RSpec assertion context."
|
343
|
+
end
|
344
|
+
|
345
|
+
context.include(RSpec::Matchers)
|
346
|
+
end
|
347
|
+
|
348
|
+
def call(unit_of_work)
|
349
|
+
super
|
350
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
351
|
+
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
DEFAULT = stack do
|
356
|
+
include Simple
|
357
|
+
include Doubles.with(:auto_verify)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|