surrogate 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme.md +52 -10
- data/lib/surrogate/hatchling.rb +28 -25
- data/lib/surrogate/invocation.rb +17 -0
- data/lib/surrogate/options.rb +2 -2
- data/lib/surrogate/rspec/abstract_failure_message.rb +59 -0
- data/lib/surrogate/rspec/have_been_asked_for_its.rb +78 -0
- data/lib/surrogate/rspec/have_been_initialized_with.rb +12 -0
- data/lib/surrogate/rspec/have_been_told_to.rb +77 -0
- data/lib/surrogate/rspec/invocation_matcher.rb +81 -0
- data/lib/surrogate/rspec/{substitutability_matchers.rb → substitute_for.rb} +0 -0
- data/lib/surrogate/rspec/times_predicate.rb +19 -0
- data/lib/surrogate/rspec/with_filter.rb +123 -0
- data/lib/surrogate/rspec.rb +25 -2
- data/lib/surrogate/values.rb +23 -30
- data/lib/surrogate/version.rb +1 -1
- data/lib/surrogate.rb +1 -0
- data/spec/acceptance_spec.rb +5 -5
- data/spec/defining_api_methods_spec.rb +19 -42
- data/spec/rspec/block_support_spec.rb +129 -0
- data/spec/rspec/messages_spec.rb +13 -10
- data/surrogate.gemspec +1 -1
- metadata +18 -9
- data/lib/surrogate/rspec/api_method_matchers.rb +0 -229
data/Readme.md
CHANGED
@@ -127,9 +127,6 @@ player.will_move 1, 9, 3
|
|
127
127
|
player.move # => 1
|
128
128
|
player.move # => 9
|
129
129
|
player.move # => 3
|
130
|
-
|
131
|
-
# then back to default behaviour (or error if none provided)
|
132
|
-
player.move # => 20
|
133
130
|
```
|
134
131
|
|
135
132
|
You can define **initialize**
|
@@ -260,8 +257,8 @@ After you've implemented the real version of your mock (assuming a [top-down](ht
|
|
260
257
|
how do you prevent your real object from getting out of synch with your mock?
|
261
258
|
|
262
259
|
Assert that your mock has the **same interface** as your real class.
|
263
|
-
This will fail if the mock inherits methods
|
264
|
-
if the real class has
|
260
|
+
This will fail if the mock inherits methods which are not on the real class. It will also fail
|
261
|
+
if the real class has any methods which have not been defined on the mock or inherited by the mock.
|
265
262
|
|
266
263
|
Presently, it will ignore methods defined directly in the mock (as it adds quite a few of its own methods,
|
267
264
|
and generally considers them to be helpers). In a future version, you will be able to tell it to treat other methods
|
@@ -325,6 +322,46 @@ MockUser.should_not substitute_for User, subset: true
|
|
325
322
|
```
|
326
323
|
|
327
324
|
|
325
|
+
Blocks
|
326
|
+
------
|
327
|
+
|
328
|
+
When your method is invoked with a block, you can make assertions about the block.
|
329
|
+
|
330
|
+
_Note: Right now, block error messages have not been addressed (which means they are probably confusing as shit)_
|
331
|
+
|
332
|
+
Before/after hooks (make assertions here)
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
class MockService
|
336
|
+
Surrogate.endow self
|
337
|
+
define(:create) {}
|
338
|
+
end
|
339
|
+
|
340
|
+
describe 'something that creates a user through the service' do
|
341
|
+
let(:old_id) { 12 }
|
342
|
+
let(:new_id) { 123 }
|
343
|
+
|
344
|
+
it 'updates the user_id and returns the old_id' do
|
345
|
+
user_id = old_id
|
346
|
+
service = MockService.new
|
347
|
+
|
348
|
+
service.create do |user|
|
349
|
+
to_return = user_id
|
350
|
+
user_id = user[:id]
|
351
|
+
to_return
|
352
|
+
end
|
353
|
+
|
354
|
+
service.should have_been_told_to(:create).with { |block|
|
355
|
+
block.call_with({id: new_id}) # this will be given to the block
|
356
|
+
block.returns old_id # provide a return value, or a block that receives the return value (where you can make assertions)
|
357
|
+
block.before { user_id.should == old_id } # assertions about state of the world before the block is called
|
358
|
+
block.after { user_id.should == new_id } # assertions about the state of the world after the block is called
|
359
|
+
}
|
360
|
+
end
|
361
|
+
end
|
362
|
+
```
|
363
|
+
|
364
|
+
|
328
365
|
How do I introduce my mocks?
|
329
366
|
============================
|
330
367
|
|
@@ -356,22 +393,27 @@ Special Thanks
|
|
356
393
|
TODO
|
357
394
|
----
|
358
395
|
|
396
|
+
* Add proper failure messages for block invocations
|
397
|
+
* Add `was told_to` syntax
|
398
|
+
* Add support for predicates
|
359
399
|
* Add a better explanation for motivations
|
360
400
|
* Figure out whether I'm supposed to be using clone or dup for the object -.^ (looks like there may also be an `initialize_copy` method I can take advantage of instead of crazy stupid shit I'm doing now)
|
361
401
|
* don't blow up when delegating to the Object#initialize with args
|
362
|
-
*
|
402
|
+
* config: rspec_mocks loaded, whether unprepared blocks should raise or just return nil
|
403
|
+
* extract surrogate/rspec into its own gem
|
404
|
+
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
405
|
+
* Add a last_instance option so you don't have to track it explicitly
|
363
406
|
|
364
407
|
|
365
408
|
Future Features
|
366
409
|
---------------
|
367
410
|
|
411
|
+
* figure out how to talk about callbacks like #on_success
|
368
412
|
* have some sort of reinitialization that can hook into setup/teardown steps of test suite
|
369
413
|
* Support arity checking as part of substitutability
|
370
|
-
* Support for blocks
|
371
414
|
* Ability to disassociate the method name from the test (e.g. you shouldn't need to change a test just because you change a name)
|
372
|
-
* declare normal methods as being part of the API
|
415
|
+
* ability to declare normal methods as being part of the API
|
416
|
+
* ability to declare a define that uses the overridden method as the body, but can still act like an api method
|
373
417
|
* assertions for order of invocations & methods
|
374
418
|
* class generator? (supports a top-down style of development for when you write your mocks before you write your implementations)
|
375
|
-
* extract surrogate/rspec into its own gem?
|
376
419
|
* deal with hard dependency on rspec-mocks
|
377
|
-
* support subset-substitutabilty not being able to touch real methods (e.g. #respond_to?)
|
data/lib/surrogate/hatchling.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
class Surrogate
|
2
|
-
|
2
|
+
SurrogateError = Class.new StandardError
|
3
|
+
UnknownMethod = Class.new SurrogateError
|
3
4
|
|
4
5
|
|
5
6
|
# This contains the unique behaviour for each instance
|
@@ -16,9 +17,10 @@ class Surrogate
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def invoke_method(method_name, args, &block)
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
invocation = Invocation.new(args, &block)
|
21
|
+
invoked_methods[method_name] << invocation
|
22
|
+
return get_default method_name, invocation, &block unless has_ivar? method_name
|
23
|
+
Value.factory(get_ivar method_name).value(method_name)
|
22
24
|
end
|
23
25
|
|
24
26
|
def prepare_method(method_name, args, &block)
|
@@ -29,7 +31,28 @@ class Surrogate
|
|
29
31
|
invoked_methods[method_name]
|
30
32
|
end
|
31
33
|
|
32
|
-
|
34
|
+
private
|
35
|
+
|
36
|
+
def invoked_methods
|
37
|
+
@invoked_methods ||= Hash.new do |hash, method_name|
|
38
|
+
must_know method_name
|
39
|
+
hash[method_name] = []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_default(method_name, invocation)
|
44
|
+
api_methods[method_name].default instance, invocation do
|
45
|
+
raise UnpreparedMethodError, "#{method_name} has been invoked without being told how to behave"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def must_know(method_name)
|
50
|
+
return if api_methods.has_key? method_name
|
51
|
+
known_methods = api_methods.keys.map(&:to_s).map(&:inspect).join ', '
|
52
|
+
raise UnknownMethod, "doesn't know \"#{method_name}\", only knows #{known_methods}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# maybe these ivar methods should be extracted into their own class
|
33
56
|
def has_ivar?(method_name)
|
34
57
|
instance.instance_variable_defined? ivar_for method_name
|
35
58
|
end
|
@@ -55,26 +78,6 @@ class Surrogate
|
|
55
78
|
end
|
56
79
|
end
|
57
80
|
|
58
|
-
private
|
59
|
-
|
60
|
-
def invoked_methods
|
61
|
-
@invoked_methods ||= Hash.new do |hash, method_name|
|
62
|
-
must_know method_name
|
63
|
-
hash[method_name] = []
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def get_default(method_name, args, &block)
|
68
|
-
api_methods[method_name].default instance, args, block do
|
69
|
-
raise UnpreparedMethodError, "#{method_name} has been invoked without being told how to behave"
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def must_know(method_name)
|
74
|
-
return if api_methods.has_key? method_name
|
75
|
-
known_methods = api_methods.keys.map(&:to_s).map(&:inspect).join ', '
|
76
|
-
raise UnknownMethod, "doesn't know \"#{method_name}\", only knows #{known_methods}"
|
77
|
-
end
|
78
81
|
end
|
79
82
|
end
|
80
83
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Surrogate
|
2
|
+
class Invocation
|
3
|
+
attr_accessor :args, :block
|
4
|
+
|
5
|
+
def initialize(args, &block)
|
6
|
+
self.args, self.block = args, block
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_block?
|
10
|
+
!!block
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(invocation)
|
14
|
+
args == invocation.args && has_block? == invocation.has_block?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/surrogate/options.rb
CHANGED
@@ -20,7 +20,7 @@ class Surrogate
|
|
20
20
|
options
|
21
21
|
end
|
22
22
|
|
23
|
-
def default(instance,
|
23
|
+
def default(instance, invocation, &no_default)
|
24
24
|
if options.has_key? :default
|
25
25
|
options[:default]
|
26
26
|
elsif default_proc
|
@@ -31,7 +31,7 @@ class Surrogate
|
|
31
31
|
# the options, then we only have to bind it to an instance and invoke
|
32
32
|
BindableBlock.new(instance.class, &default_proc)
|
33
33
|
.bind(instance)
|
34
|
-
.call(*args, &block)
|
34
|
+
.call(*invocation.args, &invocation.block)
|
35
35
|
else
|
36
36
|
no_default.call
|
37
37
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Surrogate
|
2
|
+
module RSpec
|
3
|
+
class AbstractFailureMessage
|
4
|
+
class ArgsInspector
|
5
|
+
def self.inspect(invocation)
|
6
|
+
inspected_arguments = invocation.args.map { |argument| inspect_argument argument }
|
7
|
+
inspected_arguments << 'no args' if inspected_arguments.empty?
|
8
|
+
"`" << inspected_arguments.join(", ") << "'"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inspect_argument(to_inspect)
|
12
|
+
if RSpec.rspec_mocks_loaded? && to_inspect.respond_to?(:description)
|
13
|
+
to_inspect.description
|
14
|
+
else
|
15
|
+
to_inspect.inspect
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :method_name, :invocations, :with_filter, :times_predicate
|
21
|
+
|
22
|
+
def initialize(method_name, invocations, with_filter, times_predicate)
|
23
|
+
self.method_name = method_name
|
24
|
+
self.invocations = invocations
|
25
|
+
self.with_filter = with_filter
|
26
|
+
self.times_predicate = times_predicate
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_message
|
30
|
+
raise "I should have been overridden"
|
31
|
+
end
|
32
|
+
|
33
|
+
def times_invoked
|
34
|
+
invocations.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect_arguments(arguments)
|
38
|
+
# can we fix this as soon as an array enters the system instead of catching it here?
|
39
|
+
if arguments.kind_of? Invocation
|
40
|
+
ArgsInspector.inspect arguments
|
41
|
+
else
|
42
|
+
ArgsInspector.inspect Invocation.new arguments
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def expected_invocation
|
47
|
+
with_filter.expected_invocation
|
48
|
+
end
|
49
|
+
|
50
|
+
def times_msg(n)
|
51
|
+
"#{n} time#{'s' unless n == 1}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def expected_times_invoked
|
55
|
+
times_predicate.expected_times_invoked
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'surrogate/rspec/invocation_matcher'
|
2
|
+
|
3
|
+
class Surrogate
|
4
|
+
module RSpec
|
5
|
+
class HaveBeenAskedForIts < InvocationMatcher
|
6
|
+
|
7
|
+
class FailureMessageShouldDefault < AbstractFailureMessage
|
8
|
+
def get_message
|
9
|
+
"was never asked for its #{ method_name }"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class FailureMessageShouldWith < AbstractFailureMessage
|
14
|
+
def get_message
|
15
|
+
message = "should have been asked for its #{ method_name } with #{ inspect_arguments expected_invocation }, but "
|
16
|
+
if times_invoked.zero?
|
17
|
+
message << "was never asked"
|
18
|
+
else
|
19
|
+
inspected_invocations = invocations.map { |invocation| inspect_arguments invocation }
|
20
|
+
message << "got #{inspected_invocations.join ', '}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FailureMessageShouldTimes < AbstractFailureMessage
|
26
|
+
def get_message
|
27
|
+
"should have been asked for its #{ method_name } #{ times_msg expected_times_invoked }, but was asked #{ times_msg times_invoked }"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FailureMessageWithTimes < AbstractFailureMessage
|
32
|
+
def get_message
|
33
|
+
message = "should have been asked for its #{ method_name } #{ times_msg expected_times_invoked } with #{ inspect_arguments expected_invocation }, but "
|
34
|
+
if times_invoked.zero?
|
35
|
+
message << "was never asked"
|
36
|
+
else
|
37
|
+
message << "was asked #{times_msg times_invoked}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class FailureMessageShouldNotDefault < AbstractFailureMessage
|
43
|
+
def get_message
|
44
|
+
"shouldn't have been asked for its #{ method_name }, but was asked #{ times_msg times_invoked }"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class FailureMessageShouldNotWith < AbstractFailureMessage
|
49
|
+
def get_message
|
50
|
+
message = "should not have been asked for its #{ method_name } with #{ inspect_arguments expected_invocation }, but "
|
51
|
+
if times_invoked.zero?
|
52
|
+
message << "was never asked"
|
53
|
+
else
|
54
|
+
inspected_invocations = invocations.map { |invocation| inspect_arguments invocation }
|
55
|
+
message << "got #{inspected_invocations.join ', '}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class FailureMessageShouldNotTimes < AbstractFailureMessage
|
61
|
+
def get_message
|
62
|
+
"shouldn't have been asked for its #{ method_name } #{ times_msg expected_times_invoked }, but was"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class FailureMessageShouldNotWithTimes < AbstractFailureMessage
|
67
|
+
def get_message
|
68
|
+
message = "should not have been asked for its #{ method_name } #{ times_msg expected_times_invoked } with #{ inspect_arguments expected_invocation }, "
|
69
|
+
if times_invoked.zero?
|
70
|
+
message << "was never asked"
|
71
|
+
else
|
72
|
+
message << "was asked #{times_msg times_invoked}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'surrogate/rspec/have_been_told_to'
|
2
|
+
|
3
|
+
class Surrogate
|
4
|
+
module RSpec
|
5
|
+
class HaveBeenInitializedWith < HaveBeenToldTo
|
6
|
+
def initialize(*initialization_args, &block)
|
7
|
+
super :initialize
|
8
|
+
with *initialization_args, &block
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'surrogate/rspec/invocation_matcher'
|
2
|
+
|
3
|
+
class Surrogate
|
4
|
+
module RSpec
|
5
|
+
class HaveBeenToldTo < InvocationMatcher
|
6
|
+
class FailureMessageShouldDefault < AbstractFailureMessage
|
7
|
+
def get_message
|
8
|
+
"was never told to #{ method_name }"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FailureMessageShouldWith < AbstractFailureMessage
|
13
|
+
def get_message
|
14
|
+
message = "should have been told to #{ method_name } with #{ inspect_arguments expected_invocation }, but "
|
15
|
+
if times_invoked.zero?
|
16
|
+
message << "was never told to"
|
17
|
+
else
|
18
|
+
inspected_invocations = invocations.map { |invocation| inspect_arguments invocation }
|
19
|
+
message << "got #{inspected_invocations.join ', '}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class FailureMessageShouldTimes < AbstractFailureMessage
|
25
|
+
def get_message
|
26
|
+
"should have been told to #{ method_name } #{ times_msg expected_times_invoked } but was told to #{ method_name } #{ times_msg times_invoked }"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class FailureMessageWithTimes < AbstractFailureMessage
|
31
|
+
def get_message
|
32
|
+
message = "should have been told to #{ method_name } #{ times_msg expected_times_invoked } with #{ inspect_arguments expected_invocation }, but "
|
33
|
+
if times_invoked.zero?
|
34
|
+
message << "was never told to"
|
35
|
+
else
|
36
|
+
message << "got it #{times_msg times_invoked}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class FailureMessageShouldNotDefault < AbstractFailureMessage
|
42
|
+
def get_message
|
43
|
+
"shouldn't have been told to #{ method_name }, but was told to #{ method_name } #{ times_msg times_invoked }"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class FailureMessageShouldNotWith < AbstractFailureMessage
|
48
|
+
def get_message
|
49
|
+
message = "should not have been told to #{ method_name } with #{ inspect_arguments expected_invocation }, but "
|
50
|
+
if times_invoked.zero?
|
51
|
+
message << "was never told to"
|
52
|
+
else
|
53
|
+
inspected_invocations = invocations.map { |invocation| inspect_arguments invocation }
|
54
|
+
message << "got #{inspected_invocations.join ', '}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class FailureMessageShouldNotTimes < AbstractFailureMessage
|
60
|
+
def get_message
|
61
|
+
"shouldn't have been told to #{ method_name } #{ times_msg expected_times_invoked }, but was"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class FailureMessageShouldNotWithTimes < AbstractFailureMessage
|
66
|
+
def get_message
|
67
|
+
message = "should not have been told to #{ method_name } #{ times_msg expected_times_invoked } with #{ inspect_arguments expected_invocation }, but "
|
68
|
+
if times_invoked.zero?
|
69
|
+
message << "was never told to"
|
70
|
+
else
|
71
|
+
message << "got it #{times_msg times_invoked}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'surrogate/rspec/abstract_failure_message'
|
2
|
+
require 'surrogate/rspec/times_predicate'
|
3
|
+
require 'surrogate/rspec/with_filter'
|
4
|
+
|
5
|
+
class Surrogate
|
6
|
+
module RSpec
|
7
|
+
class InvocationMatcher
|
8
|
+
attr_accessor :times_predicate, :with_filter, :surrogate, :method_name
|
9
|
+
|
10
|
+
def initialize(method_name)
|
11
|
+
self.method_name = method_name
|
12
|
+
self.times_predicate = TimesPredicate.new
|
13
|
+
self.with_filter = WithFilter.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(surrogate)
|
17
|
+
self.surrogate = surrogate
|
18
|
+
times_predicate.matches? filtered_args
|
19
|
+
end
|
20
|
+
|
21
|
+
def filtered_args
|
22
|
+
@filtered_args ||= with_filter.filter invocations
|
23
|
+
end
|
24
|
+
|
25
|
+
def invocations
|
26
|
+
surrogate.invocations(method_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure_message_for_should
|
30
|
+
raise "THIS METHOD SHOULD HAVE BEEN OVERRIDDEN"
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure_message_for_should_not
|
34
|
+
raise "THIS METHOD SHOULD HAVE BEEN OVERRIDDEN"
|
35
|
+
end
|
36
|
+
|
37
|
+
def times(times_invoked)
|
38
|
+
@times_predicate = TimesPredicate.new(times_invoked, :==)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def with(*arguments, &expectation_block)
|
43
|
+
self.with_filter = WithFilter.new arguments, :args_must_match, &expectation_block
|
44
|
+
arguments << expectation_block if expectation_block
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def failure_message_for_should
|
49
|
+
message_for(
|
50
|
+
if times_predicate.default? && with_filter.default?
|
51
|
+
:FailureMessageShouldDefault
|
52
|
+
elsif times_predicate.default?
|
53
|
+
:FailureMessageShouldWith
|
54
|
+
elsif with_filter.default?
|
55
|
+
:FailureMessageShouldTimes
|
56
|
+
else
|
57
|
+
:FailureMessageWithTimes
|
58
|
+
end
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def failure_message_for_should_not
|
63
|
+
message_for(
|
64
|
+
if times_predicate.default? && with_filter.default?
|
65
|
+
:FailureMessageShouldNotDefault
|
66
|
+
elsif times_predicate.default?
|
67
|
+
:FailureMessageShouldNotWith
|
68
|
+
elsif with_filter.default?
|
69
|
+
:FailureMessageShouldNotTimes
|
70
|
+
else
|
71
|
+
:FailureMessageShouldNotWithTimes
|
72
|
+
end
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def message_for(failure_class_name)
|
77
|
+
self.class.const_get(failure_class_name).new(method_name, invocations, with_filter, times_predicate).get_message
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Surrogate
|
2
|
+
module RSpec
|
3
|
+
class TimesPredicate
|
4
|
+
attr_accessor :expected_times_invoked, :comparer
|
5
|
+
def initialize(expected_times_invoked=0, comparer=:<)
|
6
|
+
self.expected_times_invoked = expected_times_invoked
|
7
|
+
self.comparer = comparer
|
8
|
+
end
|
9
|
+
|
10
|
+
def matches?(invocations)
|
11
|
+
expected_times_invoked.send comparer, invocations.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def default?
|
15
|
+
expected_times_invoked == 0 && comparer == :<
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
class Surrogate
|
2
|
+
module RSpec
|
3
|
+
class WithFilter
|
4
|
+
|
5
|
+
class BlockAsserter
|
6
|
+
def initialize(definition_block)
|
7
|
+
@call_with = Invocation.new []
|
8
|
+
definition_block.call self
|
9
|
+
end
|
10
|
+
|
11
|
+
def call_with(*args, &block)
|
12
|
+
@call_with = Invocation.new args, &block
|
13
|
+
end
|
14
|
+
|
15
|
+
def returns(value=nil, &block)
|
16
|
+
@returns = block || lambda { |returned| returned.should == value }
|
17
|
+
end
|
18
|
+
|
19
|
+
def before(&block)
|
20
|
+
@before = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def after(&block)
|
24
|
+
@after = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def arity(n)
|
28
|
+
@arity = n
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches?(block_to_test)
|
32
|
+
matches = before_matches? block_to_test
|
33
|
+
matches &&= return_value_matches? block_to_test
|
34
|
+
matches &&= arity_matches? block_to_test
|
35
|
+
matches &&= after_matches? block_to_test
|
36
|
+
matches
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# matches if no return specified, or returned value == specified value
|
42
|
+
def return_value_matches?(block_to_test)
|
43
|
+
returned_value = block_to_test.call(*@call_with.args, &@call_with.block)
|
44
|
+
@returns.call returned_value if @returns
|
45
|
+
true
|
46
|
+
rescue ::RSpec::Expectations::ExpectationNotMetError
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# matches if the first time it is called, it raises nothing
|
51
|
+
def before_matches?(*)
|
52
|
+
@before_has_been_invoked || (@before && @before.call)
|
53
|
+
ensure
|
54
|
+
return @before_has_been_invoked = true unless $!
|
55
|
+
end
|
56
|
+
|
57
|
+
# matches if nothing is raised
|
58
|
+
def after_matches?(block_to_test)
|
59
|
+
@after && @after.call
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def arity_matches?(block_to_test)
|
64
|
+
return true unless @arity
|
65
|
+
block_to_test.arity == @arity
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_accessor :block_to_test
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
# rename args to invocation
|
75
|
+
attr_accessor :expected_invocation, :block, :pass, :filter_name
|
76
|
+
|
77
|
+
def initialize(args=[], filter_name=:default_filter, &block)
|
78
|
+
self.expected_invocation = Invocation.new args.dup, &block
|
79
|
+
self.block = block
|
80
|
+
self.pass = send filter_name
|
81
|
+
self.filter_name = filter_name
|
82
|
+
end
|
83
|
+
|
84
|
+
def filter(invocations)
|
85
|
+
invocations.select &pass
|
86
|
+
end
|
87
|
+
|
88
|
+
def default?
|
89
|
+
filter_name == :default_filter
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def default_filter
|
95
|
+
Proc.new { true }
|
96
|
+
end
|
97
|
+
|
98
|
+
def args_must_match
|
99
|
+
lambda { |invocation| args_match?(invocation) && blocks_match?(invocation) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def blocks_match?(actual_invocation)
|
103
|
+
# surely this is wrong
|
104
|
+
return true unless expected_invocation.has_block?
|
105
|
+
return unless actual_invocation.has_block?
|
106
|
+
block_asserter.matches? actual_invocation.block
|
107
|
+
end
|
108
|
+
|
109
|
+
def block_asserter
|
110
|
+
@block_asserter ||= BlockAsserter.new expected_invocation.block
|
111
|
+
end
|
112
|
+
|
113
|
+
def args_match?(actual_invocation)
|
114
|
+
if RSpec.rspec_mocks_loaded?
|
115
|
+
rspec_arg_expectation = ::RSpec::Mocks::ArgumentExpectation.new *expected_invocation.args
|
116
|
+
rspec_arg_expectation.args_match? *actual_invocation.args
|
117
|
+
else
|
118
|
+
expected_arguments == actual_arguments
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|