xspec 0.0.2 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43babe397ea759864cca9b9cf9f7b3f85574df64
4
- data.tar.gz: 9bb1e2521e47fd442469f79c15f5ac0bb785a010
3
+ metadata.gz: a5dba5d7629f6d87591f9abe883290f50372dc64
4
+ data.tar.gz: ead15780cdec15272984c1580051e7d7f2eadded
5
5
  SHA512:
6
- metadata.gz: 4a3278a29ac464e56907888124acf872c5151282718286ee141cd57e933e2b28e15d817c81e20b1a5f29199b2097ba962264190d7df44b604ab974ddfed3e76f
7
- data.tar.gz: 652428e1709ecb942d8e858f764489cf5681d40b8b09e4061c9b2814a93f956ddc8df5d48b39fc382b6ec755f455a2c3df8fb4fcf7f93bea7aebdd10b808fb3d
6
+ metadata.gz: b37433a77450176d0cf1ebeae40d5ad24ef28ce8095887126edfd5dfd0040fdcfbf418a1726862a60eb7f275f347d19a371720331b5376145e256bff08374ef5
7
+ data.tar.gz: 963b1b472454aeaeb03aa956a1e38ff3a9b1a9bb4bf78fcd8c9eb06aae26a31d4591ed8e7b9a4fa1c11f43a7413a2d37112d2bd140c182145fcadc8745922b91
data/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  XSpec
2
2
  =====
3
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/).
4
+ XSpec is an rspec-inspired testing library for Ruby that is written in a
5
+ literate style and designed to be obvious to use, highly modular, and easy to
6
+ extend.
9
7
 
10
8
  Usage
11
9
  -----
12
10
 
11
+ gem install xspec
12
+
13
13
  The default configuration XSpec provides a number of interesting features:
14
14
  assertions, doubles, and rich output.
15
15
 
@@ -40,7 +40,7 @@ Running this with the built-in runner generates some pretty output. You can't
40
40
  see the colors in this README, but trust me they are quite lovely.
41
41
 
42
42
  ```
43
- > bin/xspec example.rb
43
+ > xspec example.rb
44
44
 
45
45
  my application
46
46
  0.000s does math
@@ -61,8 +61,7 @@ my application fails:
61
61
  bin/xspec:19:in `<main>'
62
62
  ```
63
63
 
64
- Customization
65
- -------------
64
+ ### Customization
66
65
 
67
66
  Every aspect of XSpec is customizable, from how tests are scheduled and run all
68
67
  the way through to formatting of output.
@@ -86,56 +85,29 @@ describe '...' do
86
85
  end
87
86
  ```
88
87
 
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
88
+ Of course, you can make your own extension classes as well. For details, see
89
+ the "Configuration" section of the documentation.
108
90
 
109
- notifier.evaluate_start(x)
110
-
111
- errors = x.immediate_parent.execute(x)
112
- duration = rand
113
- result = XSpec::ExecutedUnitOfWork.new(x, errors, duration)
91
+ Documentation
92
+ -------------
114
93
 
115
- notifier.evaluate_finish(result)
116
- end
94
+ There are two major sources of documentation:
117
95
 
118
- notifier.run_finish
119
- end
120
- end
96
+ * [Main API documentation.](https://xaviershay.github.io/xspec/api.html)
97
+ * [Literate source code.](https://xaviershay.github.io/xspec/)
121
98
 
122
- extend XSpec.dsl(
123
- evaluator: UnhelpfulRunner.new(notifier: XSpec::Notifier::DEFAULT)
124
- )
125
-
126
- describe '...' do
127
- # etc etc
128
- end
129
- ```
99
+ It is expected that regular users of XSpec will read both at least once. There
100
+ isn't much to them, and they will give you a useful mental model of how XSpec
101
+ works.
130
102
 
131
103
  Developing
132
104
  ----------
133
105
 
134
106
  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:
107
+ a traditional Ruby project. Bug fixes welcome, though features are likely to be
108
+ rejected since I have a strong opinion of what this library should and should
109
+ not do. Talk to me before embarking on anything large. Tests are written in
110
+ XSpec, which might do your head in:
139
111
 
140
112
  bundle install
141
- bin/test
113
+ bundle exec bin/xspec
data/bin/xspec CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require 'xspec'
6
+
3
7
  $LOAD_PATH.unshift "spec"
4
8
  $LOAD_PATH.unshift "lib"
5
9
 
@@ -9,8 +13,6 @@ else
9
13
  Dir['spec/**/*_spec.rb']
10
14
  end
11
15
 
12
- require 'xspec'
13
-
14
16
  files.each do |f|
15
17
  load f
16
18
  end
data/lib/xspec.rb CHANGED
@@ -16,13 +16,16 @@ module XSpec
16
16
  options = XSpec.add_defaults(options)
17
17
 
18
18
  Module.new do
19
+ # Each DSL provides a standard set of methods provided by the [DSL
20
+ # module](dsl.html).
19
21
  include DSL
20
22
 
21
- # Each DSL has its own independent context, which is described in detail
22
- # in `data_structures.rb`.
23
+ # In addition, each DSL has its own independent context, which is
24
+ # described in detail in the
25
+ # [`data_structures.rb`](data_structures.html).
23
26
  def __xspec_context
24
- assertion_context = __xspec_opts.fetch(:assertion_context)
25
- @__xspec_context ||= XSpec::Context.root(assertion_context)
27
+ evaluator = __xspec_opts.fetch(:evaluator)
28
+ @__xspec_context ||= XSpec::Context.root(evaluator)
26
29
  end
27
30
 
28
31
  # Some meta-magic is needed to enable the options from local scope above
@@ -31,9 +34,12 @@ module XSpec
31
34
 
32
35
  # `run!` is where the magic happens. Typically called at the end of a
33
36
  # 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.
37
+ # accumulated by the DSL methods above and runs it through the scheduler.
35
38
  def run!
36
- __xspec_opts.fetch(:evaluator).run(__xspec_context)
39
+ notifier = __xspec_opts.fetch(:notifier)
40
+ scheduler = __xspec_opts.fetch(:scheduler)
41
+
42
+ scheduler.run(__xspec_context, notifier)
37
43
  end
38
44
 
39
45
  # It is often convenient to trigger a run after all files have been
@@ -49,13 +55,14 @@ module XSpec
49
55
  module_function :dsl
50
56
  end
51
57
 
52
- # Understanding the data structures used by XSpec will assist you in
53
- # understanding the behavoural components such as the evaluator and notifier.
58
+ # Understanding the [data structures](data_structures.html) used by XSpec will
59
+ # assist you in understanding the behavoural components such as the scheduler
60
+ # and notifier. Read it next.
54
61
  require 'xspec/data_structures'
55
62
 
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.
63
+ # To further explore the code base, dive into the [defaults
64
+ # file](defaults.html), which describes the different sub-components of XSpec
65
+ # that you can use or customize.
59
66
  require 'xspec/defaults'
60
67
 
61
68
  require 'xspec/dsl'
@@ -7,7 +7,7 @@
7
7
  module XSpec
8
8
  # A unit of work, usually created by the `it` DSL method, is a labeled,
9
9
  # indivisible code block that expresses an assertion about a property of the
10
- # system under test. They are run by an evaluator.
10
+ # system under test. They are run by a scheduler.
11
11
  UnitOfWork = Struct.new(:name, :block)
12
12
 
13
13
 
@@ -20,7 +20,7 @@ module XSpec
20
20
  require 'xspec/dsl'
21
21
  class Context
22
22
  class << self
23
- attr_reader :name, :children, :units_of_work, :assertion_context
23
+ attr_reader :name, :children, :units_of_work, :evaluator
24
24
 
25
25
  # A context includes the same DSL methods as the root level module, which
26
26
  # enables the recursive creation.
@@ -29,33 +29,33 @@ module XSpec
29
29
 
30
30
  # Each nested context creates a new class that inherits from the parent.
31
31
  # Methods can be added to this class as per normal, and are correctly
32
- # inherited by children. When it comes time to run tests, the evaluator
32
+ # inherited by children. When it comes time to run tests, the scheduler
33
33
  # will create a new instance of the context (a class) for each test,
34
34
  # making the defined methods available and also ensuring that there is no
35
35
  # state pollution between tests.
36
- def make(name, assertion_context, &block)
36
+ def make(name, evaluator, &block)
37
37
  x = Class.new(self)
38
- x.initialize!(name, assertion_context)
38
+ x.initialize!(name, evaluator)
39
39
  x.class_eval(&block) if block
40
- x.apply_assertion_context!
40
+ x.apply_evaluator!
41
41
  x
42
42
  end
43
43
 
44
44
  # A class cannot have an implicit initializer, but some variable
45
45
  # inititialization is required so the `initialize!` method is called
46
46
  # explicitly when ever a dynamic subclass is created.
47
- def initialize!(name, assertion_context)
47
+ def initialize!(name, evaluator)
48
48
  @children = []
49
49
  @units_of_work = []
50
50
  @name = name
51
- @assertion_context = assertion_context
51
+ @evaluator = evaluator
52
52
  end
53
53
 
54
54
  # The assertion context should be applied after the user has had a chance
55
55
  # to add their own methods. It needs to be last so that users can't
56
56
  # clobber the assertion methods.
57
- def apply_assertion_context!
58
- include(assertion_context)
57
+ def apply_evaluator!
58
+ include(evaluator)
59
59
  end
60
60
 
61
61
  # Executing a unit of work creates a new instance and hands it off to the
@@ -68,14 +68,14 @@ module XSpec
68
68
 
69
69
  # The root context is nothing special, and behaves the same as all the
70
70
  # others.
71
- def root(assertion_context)
72
- make(nil, assertion_context)
71
+ def root(evaluator)
72
+ make(nil, evaluator)
73
73
  end
74
74
 
75
75
  # Child contexts and units of work are typically added by the `describe`
76
76
  # and `it` DSL methods respectively.
77
77
  def add_child_context(name = nil, opts = {}, &block)
78
- self.children << make(name, assertion_context, &block)
78
+ self.children << make(name, evaluator, &block)
79
79
  end
80
80
 
81
81
  def add_unit_of_work(name = nil, opts = {}, &block)
@@ -91,13 +91,13 @@ module XSpec
91
91
  # This is leaky abstraction, since only units of work are copied from
92
92
  # shared contexts. Methods and child contexts are ignored.
93
93
  def create_shared_context(&block)
94
- make(nil, assertion_context, &block)
94
+ make(nil, evaluator, &block)
95
95
  end
96
96
 
97
97
  def copy_into_tree(source_context)
98
98
  target_context = make(
99
99
  source_context.name,
100
- source_context.assertion_context
100
+ source_context.evaluator
101
101
  )
102
102
  source_context.nested_units_of_work.each do |x|
103
103
  target_context.units_of_work << x.unit_of_work
@@ -2,9 +2,9 @@
2
2
 
3
3
  # These are the defaults used by `XSpec.dsl`, but feel free to specify your own
4
4
  # instead. They are set up in such a way that if you can override a component
5
- # down in the bowels without having to provide an entire top level evaluator.
5
+ # down in the bowels without having to provide an entire top level scheduler.
6
+ require 'xspec/schedulers'
6
7
  require 'xspec/evaluators'
7
- require 'xspec/assertion_contexts'
8
8
  require 'xspec/notifiers'
9
9
 
10
10
  module XSpec
@@ -18,12 +18,12 @@ module XSpec
18
18
  # This is a module that is included as the final step in constructing a
19
19
  # context. Allows for different matchers and expectation frameworks to be
20
20
  # used.
21
- options[:assertion_context] ||= AssertionContext::DEFAULT
21
+ options[:evaluator] ||= Evaluator::DEFAULT
22
22
 
23
- # An evaluator is responsible for scheduling units of work and handing them
23
+ # An scheduler is responsible for scheduling units of work and handing them
24
24
  # off to the assertion context. Any logic regarding threads, remote
25
- # execution or the like belongs in an evaluator.
26
- options[:evaluator] ||= Evaluator::DEFAULT.new(options)
25
+ # execution or the like belongs in a scheduler.
26
+ options[:scheduler] ||= Scheduler::DEFAULT
27
27
  options
28
28
  end
29
29
  module_function :add_defaults
data/lib/xspec/dsl.rb CHANGED
@@ -1,6 +1,11 @@
1
+ # # DSL
2
+
1
3
  # Common DSL functions are provided as a module so that they can be used in
2
4
  # both top-level and nested contexts. The method names are modeled after
3
5
  # rspec, and should behave roughly the same.
6
+ #
7
+ # They delegate to method in the [current context](xspec.html#section-5) named
8
+ # in a way that more accurately represents XSpec implementation details.
4
9
  module XSpec
5
10
  module DSL
6
11
  def it(*args, &block)
@@ -1,40 +1,334 @@
1
1
  # # Evaluators
2
2
 
3
- # Evaluators are responsible for collecting all units of work to be run and
4
- # scheduling them.
3
+ # Evaluators are usually composed together into a stack. The final stack has a
4
+ # single API method `call`, which is sent the unit of work to be executed and
5
+ # must return an array of `Failure` objects. It should not allow code-level
6
+ # exceptions to be raised, though should not block system exceptions
7
+ # (`SignalException`, `NoMemoryError`, etc).
5
8
  module XSpec
6
9
  module Evaluator
7
- # The serial evaluator, unsurprisingly, runs all units of works serially in
8
- # a loop. It is about as simple an evaluator as you can imagine. Parents
9
- # are responsible for actually executing the work.
10
- class Serial
11
- def initialize(opts)
12
- @notifier = opts.fetch(:notifier)
13
- @clock = opts.fetch(:clock, ->{ Time.now.to_f })
10
+ # A stack is typically book-ended by the top and bottom evaluators, 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 evaluator executes the unit of work, with no error handling or
21
+ # extra behaviour. By separating this, all other evaluators layered on top
22
+ # of 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 usually be included as the final module in a stack. It is
31
+ # a catch all to make sure all standard exceptions have been handled and do
32
+ # 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 evaluator 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 the following options:
111
+ #
112
+ # * `strict` forbids doubling of classes that have not been loaded. This
113
+ # should generally be enabled when doing a full spec run, and disabled
114
+ # when running specs in isolation.
115
+ #
116
+ # The `with` method returns a module that can be included in a stack.
117
+ def self.with(*opts)
118
+ modules = [self] + opts.map {|x| {
119
+ strict: Strict
120
+ }.fetch(x) }
121
+
122
+
123
+ Module.new do
124
+ modules.each do |m|
125
+ include m
126
+ end
127
+ end
128
+ end
129
+
130
+ # An instance double stands in for an instance of the given class
131
+ # reference, given as a string. The class does not need to be loaded, but
132
+ # if it is then only public instance methods defined on the class are
133
+ # able to be expected.
134
+ def instance_double(klass)
135
+ _double(klass, InstanceReference)
136
+ end
137
+
138
+ # Simarly, a class double validates that class responds to all expected
139
+ # methods, if that class has been loaded.
140
+ def class_double(klass)
141
+ _double(klass, ClassReference)
142
+ end
143
+
144
+ # If the doubled class has not been loaded, a null object reference is
145
+ # used that allows expecting of all methods.
146
+ def _double(klass, type)
147
+ ref = if self.class.const_defined?(klass)
148
+ type.new(self.class.const_get(klass))
149
+ else
150
+ StringReference.new(klass)
151
+ end
152
+
153
+ Double.new(ref)
14
154
  end
15
155
 
16
- def run(context)
17
- notifier.run_start
156
+ # Use `verify` to assert that a method was called on a double with
157
+ # particular arguments. Doubles record all received messages, so `verify`
158
+ # should be called at the end of your test.
159
+ def verify(obj)
160
+ Proxy.new(obj, :_verify)
161
+ end
162
+
163
+
164
+ # All messages sent to a double will return `nil`. Use `stub` to specify
165
+ # a return value instead: `stub(double).some_method(1, 2) { "return
166
+ # value" }`. This must be called before the message is sent to the
167
+ # double.
168
+ def stub(obj)
169
+ Proxy.new(obj, :_stub)
170
+ end
171
+
172
+ # The proxy object captures messages sent to it and passes them through
173
+ # to either the `_verify` of `_stub` method on the double.
174
+ class Proxy < BasicObject
175
+ def initialize(double, method)
176
+ @double = double
177
+ @method = method
178
+ end
179
+
180
+ def method_missing(*args, &ret)
181
+ @double.__send__(@method, args, &(ret || ->{}))
182
+ end
183
+ end
184
+
185
+ # Since the double object inherits from `BasicObject`, virtually every
186
+ # method call will be routed through `method_missing`. From there, the
187
+ # message can be recorded and an appropriate return value selected from
188
+ # the stubs.
189
+ class Double < BasicObject
190
+ def initialize(klass)
191
+ @klass = klass
192
+ @expected = []
193
+ @received = []
194
+ end
195
+
196
+ def method_missing(*actual_args)
197
+ stub = @expected.find {|expected_args, ret|
198
+ expected_args == actual_args
199
+ }
200
+
201
+ ret = if stub
202
+ stub[1].call
203
+ end
204
+
205
+ @received << actual_args
206
+
207
+ ret
208
+ end
209
+
210
+ # The two methods needed on this object to set it up and verify it are
211
+ # prefixed by `_` to try to ensure they don't clash with any method
212
+ # expectations. While not fail-safe, users should only be using
213
+ # expectations for a public API, and `_` is traditionally only used
214
+ # for private methods (if at all).
215
+ def _stub(args, &ret)
216
+ @klass.validate_call! args
217
+
218
+ @expected << [args, ret]
219
+ end
220
+
221
+ def _verify(args)
222
+ @klass.validate_call! args
223
+
224
+ i = @received.index(args)
225
+
226
+ if i
227
+ @received.delete_at(i)
228
+ else
229
+ name, rest = *args
230
+ ::Kernel.raise DoubleFailure,
231
+ "Did not receive: %s(%s)\nDid receive:%s\n" % [
232
+ name,
233
+ [*rest].map(&:inspect).join(", "),
234
+ @received.map {|name, *args|
235
+ " %s(%s)" % [name, args.map(&:inspect).join(", ")]
236
+ }.join("\n")
237
+ ]
238
+ end
239
+ end
240
+ end
241
+
242
+ # A reference can be thought of as a "backing object" for a double. It
243
+ # provides an API to validate that a method being expected actually
244
+ # exists - the implementation is different for the different types of
245
+ # doubles.
246
+ class Reference
247
+ def initialize(klass)
248
+ @klass = klass
249
+ end
250
+
251
+ def validate_call!(args)
252
+ end
253
+
254
+ def to_s
255
+ @klass.to_s
256
+ end
257
+ end
18
258
 
19
- context.nested_units_of_work.each do |x|
20
- notifier.evaluate_start(x)
259
+ # A string reference is the "null object" of references, allowing all
260
+ # methods to be expected. It is used when nothing is known about the
261
+ # referenced class (such as when it has not been loaded).
262
+ class StringReference < Reference
263
+ end
21
264
 
22
- start_time = clock.()
23
- errors = x.immediate_parent.execute(x)
24
- finish_time = clock.()
265
+ # Class and Instance references are backed by loaded classes, and
266
+ # restrict the messages that can be expected on a double.
267
+ class ClassReference < Reference
268
+ def validate_call!(args)
269
+ name, rest = *args
25
270
 
26
- result = ExecutedUnitOfWork.new(x, errors, finish_time - start_time)
27
- notifier.evaluate_finish(result)
271
+ unless @klass.respond_to?(name)
272
+ raise DoubleFailure,
273
+ "#{@klass}.#{name} is unimplemented or not public"
274
+ end
28
275
  end
276
+ end
277
+
278
+ class InstanceReference < Reference
279
+ def validate_call!(args)
280
+ name, rest = *args
29
281
 
30
- notifier.run_finish
282
+ unless @klass.public_instance_methods.include?(name)
283
+ raise DoubleFailure,
284
+ "#{@klass}##{name} is unimplemented or not public"
285
+ end
286
+ end
31
287
  end
32
288
 
33
- protected
289
+ # The `:strict` option mixes in this `Strict` module, which raises rather
290
+ # than create a `StringReference` for unknown classes.
291
+ module Strict
292
+ def _double(klass, type)
293
+ ref = if self.class.const_defined?(klass)
294
+ type.new(self.class.const_get(klass))
295
+ else
296
+ raise DoubleFailure, "#{klass} is not a valid class name"
297
+ end
34
298
 
35
- attr_reader :notifier, :clock
299
+ super
300
+ end
301
+ end
36
302
  end
37
303
 
38
- DEFAULT = Serial
304
+
305
+ # ### RSpec Integration
306
+ #
307
+ # This RSpec adapter shows two useful techniques: dynamic library loading
308
+ # which removes RSpec as a direct dependency, and use of the `mixin`
309
+ # method to further extend the target evalutor.
310
+ module RSpecExpectations
311
+ def self.included(mod)
312
+ begin
313
+ require 'rspec/expectations'
314
+ require 'rspec/matchers'
315
+ rescue LoadError
316
+ raise "RSpec is not available, cannot use RSpec assertion context."
317
+ end
318
+
319
+ mod.include(RSpec::Matchers)
320
+ end
321
+
322
+ def call(unit_of_work)
323
+ super
324
+ rescue RSpec::Expectations::ExpectationNotMetError => e
325
+ [Failure.new(unit_of_work, e.message, e.backtrace)]
326
+ end
327
+ end
328
+
329
+ DEFAULT = stack do
330
+ include Simple
331
+ include Doubles
332
+ end
39
333
  end
40
334
  end