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