mocktail 0.0.1 → 0.0.5
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 +4 -4
- data/.github/workflows/main.yml +9 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile.lock +2 -1
- data/README.md +242 -65
- data/bin/console +47 -1
- data/lib/mocktail/explains_thing.rb +132 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +13 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +4 -0
- data/lib/mocktail/handles_dry_call/validates_arguments.rb +2 -23
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +29 -23
- data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +21 -0
- data/lib/mocktail/imitates_type/makes_double.rb +8 -4
- data/lib/mocktail/raises_neato_no_method_error.rb +81 -0
- data/lib/mocktail/share/creates_identifier.rb +28 -0
- data/lib/mocktail/{verifies_call/raises_verification_error → share}/stringifies_call.rb +16 -7
- data/lib/mocktail/share/stringifies_method_name.rb +11 -0
- data/lib/mocktail/simulates_argument_error/cleans_backtrace.rb +15 -0
- data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +20 -0
- data/lib/mocktail/simulates_argument_error/recreates_message.rb +29 -0
- data/lib/mocktail/simulates_argument_error/transforms_params.rb +32 -0
- data/lib/mocktail/simulates_argument_error.rb +30 -0
- data/lib/mocktail/value/cabinet.rb +12 -0
- data/lib/mocktail/value/double.rb +7 -8
- data/lib/mocktail/value/double_data.rb +10 -0
- data/lib/mocktail/value/explanation.rb +26 -0
- data/lib/mocktail/value/signature.rb +36 -0
- data/lib/mocktail/value/stub_returned_nil.rb +26 -0
- data/lib/mocktail/value/top_shelf.rb +24 -25
- data/lib/mocktail/value/type_replacement_data.rb +13 -0
- data/lib/mocktail/value/unsatisfied_stubbing.rb +8 -0
- data/lib/mocktail/value.rb +6 -0
- data/lib/mocktail/verifies_call/raises_verification_error.rb +4 -2
- data/lib/mocktail/version.rb +1 -1
- data/lib/mocktail.rb +9 -0
- data/mocktail.gemspec +2 -2
- metadata +22 -6
- data/lib/mocktail/share/simulates_argument_error.rb +0 -28
@@ -1,15 +1,19 @@
|
|
1
1
|
require_relative "fulfills_stubbing/finds_satisfaction"
|
2
|
+
require_relative "fulfills_stubbing/describes_unsatisfied_stubbing"
|
2
3
|
|
3
4
|
module Mocktail
|
4
5
|
class FulfillsStubbing
|
5
6
|
def initialize
|
6
7
|
@finds_satisfaction = FindsSatisfaction.new
|
8
|
+
@describes_unsatisfied_stubbing = DescribesUnsatisfiedStubbing.new
|
7
9
|
end
|
8
10
|
|
9
11
|
def fulfill(dry_call)
|
10
12
|
if (stubbing = satisfaction(dry_call))
|
11
13
|
stubbing.satisfied!
|
12
14
|
stubbing.effect&.call(dry_call)
|
15
|
+
else
|
16
|
+
StubReturnedNil.new(@describes_unsatisfied_stubbing.describe(dry_call))
|
13
17
|
end
|
14
18
|
end
|
15
19
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require_relative "../share/simulates_argument_error"
|
2
|
-
|
3
1
|
module Mocktail
|
4
2
|
class ValidatesArguments
|
5
3
|
def self.disable!
|
@@ -30,28 +28,9 @@ module Mocktail
|
|
30
28
|
def validate(dry_call)
|
31
29
|
return if self.class.disabled?
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
}.partition { |type, _|
|
36
|
-
[:req, :opt, :rest].include?(type)
|
37
|
-
}
|
38
|
-
|
39
|
-
unless args_match?(arg_params, dry_call.args) &&
|
40
|
-
kwargs_match?(kwarg_params, dry_call.kwargs)
|
41
|
-
raise @simulates_argument_error.simulate(arg_params, dry_call.args, kwarg_params, dry_call.kwargs)
|
31
|
+
if (error = @simulates_argument_error.simulate(dry_call))
|
32
|
+
raise error
|
42
33
|
end
|
43
34
|
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def args_match?(arg_params, args)
|
48
|
-
args.size >= arg_params.count { |type, _| type == :req } &&
|
49
|
-
(arg_params.any? { |type, _| type == :rest } || args.size <= arg_params.size)
|
50
|
-
end
|
51
|
-
|
52
|
-
def kwargs_match?(kwarg_params, kwargs)
|
53
|
-
kwarg_params.select { |type, _| type == :keyreq }.all? { |_, name| kwargs.key?(name) } &&
|
54
|
-
(kwarg_params.any? { |type, _| type == :keyrest } || kwargs.keys.all? { |name| kwarg_params.any? { |_, key| name == key } })
|
55
|
-
end
|
56
35
|
end
|
57
36
|
end
|
@@ -2,13 +2,12 @@ module Mocktail
|
|
2
2
|
class DeclaresDryClass
|
3
3
|
def initialize
|
4
4
|
@handles_dry_call = HandlesDryCall.new
|
5
|
+
@raises_neato_no_method_error = RaisesNeatoNoMethodError.new
|
5
6
|
end
|
6
7
|
|
7
|
-
def declare(type)
|
8
|
-
type_type = type_of(type)
|
9
|
-
instance_methods = instance_methods_on(type)
|
8
|
+
def declare(type, instance_methods)
|
10
9
|
dry_class = Class.new(Object) {
|
11
|
-
include type if
|
10
|
+
include type if type.instance_of?(Module)
|
12
11
|
|
13
12
|
def initialize(*args, **kwargs, &blk)
|
14
13
|
end
|
@@ -18,15 +17,19 @@ module Mocktail
|
|
18
17
|
}
|
19
18
|
alias_method :kind_of?, :is_a?
|
20
19
|
|
21
|
-
if
|
20
|
+
if type.instance_of?(Class)
|
22
21
|
define_method :instance_of?, ->(thing) {
|
23
22
|
type == thing
|
24
23
|
}
|
25
24
|
end
|
26
25
|
}
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
# These have special implementations, but if the user defines
|
28
|
+
# any of them on the object itself, then they'll be replaced with normal
|
29
|
+
# mocked methods. YMMV
|
30
|
+
add_stringify_methods!(dry_class, :to_s, type, instance_methods)
|
31
|
+
add_stringify_methods!(dry_class, :inspect, type, instance_methods)
|
32
|
+
define_method_missing_errors!(dry_class, type, instance_methods)
|
30
33
|
|
31
34
|
define_double_methods!(dry_class, type, instance_methods)
|
32
35
|
|
@@ -43,7 +46,7 @@ module Mocktail
|
|
43
46
|
singleton: false,
|
44
47
|
double: self,
|
45
48
|
original_type: type,
|
46
|
-
dry_type:
|
49
|
+
dry_type: dry_class,
|
47
50
|
method: method,
|
48
51
|
original_method: type.instance_method(method),
|
49
52
|
args: args,
|
@@ -54,7 +57,7 @@ module Mocktail
|
|
54
57
|
end
|
55
58
|
end
|
56
59
|
|
57
|
-
def add_stringify_methods!(dry_class, method_name, type,
|
60
|
+
def add_stringify_methods!(dry_class, method_name, type, instance_methods)
|
58
61
|
dry_class.define_singleton_method method_name, -> {
|
59
62
|
if (id_matches = super().match(/:([0-9a-fx]+)>$/))
|
60
63
|
"#<Class #{"including module " if type.instance_of?(Module)}for mocktail of #{type.name}:#{id_matches[1]}>"
|
@@ -74,22 +77,25 @@ module Mocktail
|
|
74
77
|
end
|
75
78
|
end
|
76
79
|
|
77
|
-
def
|
78
|
-
if
|
79
|
-
:class
|
80
|
-
elsif type.is_a?(Module)
|
81
|
-
:module
|
82
|
-
end
|
83
|
-
end
|
80
|
+
def define_method_missing_errors!(dry_class, type, instance_methods)
|
81
|
+
return if instance_methods.include?(:method_missing)
|
84
82
|
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
raises_neato_no_method_error = @raises_neato_no_method_error
|
84
|
+
dry_class.define_method :method_missing, ->(name, *args, **kwargs, &block) {
|
85
|
+
raises_neato_no_method_error.call(
|
86
|
+
Call.new(
|
87
|
+
singleton: false,
|
88
|
+
double: self,
|
89
|
+
original_type: type,
|
90
|
+
dry_type: self.class,
|
91
|
+
method: name,
|
92
|
+
original_method: nil,
|
93
|
+
args: args,
|
94
|
+
kwargs: kwargs,
|
95
|
+
block: block
|
96
|
+
)
|
97
|
+
)
|
88
98
|
}
|
89
99
|
end
|
90
|
-
|
91
|
-
def ignored_ancestors
|
92
|
-
Object.ancestors
|
93
|
-
end
|
94
100
|
end
|
95
101
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class GathersFakeableInstanceMethods
|
3
|
+
def gather(type)
|
4
|
+
methods = type.instance_methods + [
|
5
|
+
(:respond_to_missing? if type.private_method_defined?(:respond_to_missing?))
|
6
|
+
].compact
|
7
|
+
|
8
|
+
methods.reject { |m|
|
9
|
+
ignore?(type, m)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def ignore?(type, method_name)
|
14
|
+
ignored_ancestors.include?(type.instance_method(method_name).owner)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ignored_ancestors
|
18
|
+
Object.ancestors
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
1
|
require_relative "makes_double/declares_dry_class"
|
2
|
+
require_relative "makes_double/gathers_fakeable_instance_methods"
|
2
3
|
|
3
4
|
module Mocktail
|
4
5
|
class MakesDouble
|
5
6
|
def initialize
|
6
7
|
@declares_dry_class = DeclaresDryClass.new
|
8
|
+
@gathers_fakeable_instance_methods = GathersFakeableInstanceMethods.new
|
7
9
|
end
|
8
10
|
|
9
|
-
def make(
|
10
|
-
|
11
|
+
def make(type)
|
12
|
+
dry_methods = @gathers_fakeable_instance_methods.gather(type)
|
13
|
+
dry_type = @declares_dry_class.declare(type, dry_methods)
|
11
14
|
Double.new(
|
12
|
-
original_type:
|
15
|
+
original_type: type,
|
13
16
|
dry_type: dry_type,
|
14
|
-
dry_instance: dry_type.new
|
17
|
+
dry_instance: dry_type.new,
|
18
|
+
dry_methods: dry_methods
|
15
19
|
)
|
16
20
|
end
|
17
21
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative "share/stringifies_call"
|
2
|
+
require_relative "share/stringifies_method_name"
|
3
|
+
require_relative "share/creates_identifier"
|
4
|
+
|
5
|
+
module Mocktail
|
6
|
+
class RaisesNeatoNoMethodError
|
7
|
+
def initialize
|
8
|
+
@stringifies_call = StringifiesCall.new
|
9
|
+
@stringifies_method_name = StringifiesMethodName.new
|
10
|
+
@creates_identifier = CreatesIdentifier.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(call)
|
14
|
+
raise NoMethodError.new <<~MSG
|
15
|
+
No method `#{@stringifies_method_name.stringify(call)}' exists for call:
|
16
|
+
|
17
|
+
#{@stringifies_call.stringify(call, anonymous_blocks: true, always_parens: true)}
|
18
|
+
|
19
|
+
Need to define the method? Here's a sample definition:
|
20
|
+
|
21
|
+
def #{call.method}#{params(call)}
|
22
|
+
end
|
23
|
+
#{corrections(call)}
|
24
|
+
MSG
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def params(call)
|
30
|
+
return if (params_lists = [
|
31
|
+
params_list(call.args),
|
32
|
+
kwparams_list(call.kwargs),
|
33
|
+
block_param(call.block)
|
34
|
+
].compact).empty?
|
35
|
+
|
36
|
+
"(#{params_lists.join(", ")})"
|
37
|
+
end
|
38
|
+
|
39
|
+
def params_list(args)
|
40
|
+
return if args.empty?
|
41
|
+
|
42
|
+
count_repeats(args.map { |arg|
|
43
|
+
@creates_identifier.create(arg, default: "arg")
|
44
|
+
}).join(", ")
|
45
|
+
end
|
46
|
+
|
47
|
+
def kwparams_list(kwargs)
|
48
|
+
return if kwargs.empty?
|
49
|
+
|
50
|
+
kwargs.keys.map { |key| "#{key}:" }.join(", ")
|
51
|
+
end
|
52
|
+
|
53
|
+
def block_param(block)
|
54
|
+
return if block.nil?
|
55
|
+
|
56
|
+
"&blk"
|
57
|
+
end
|
58
|
+
|
59
|
+
def count_repeats(identifiers)
|
60
|
+
identifiers.map.with_index { |id, i|
|
61
|
+
if (preceding_matches = identifiers[0...i].count { |other_id| id == other_id }) > 0
|
62
|
+
"#{id}#{preceding_matches + 1}"
|
63
|
+
else
|
64
|
+
id
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def corrections(call)
|
70
|
+
return if (corrections = DidYouMean::SpellChecker.new(dictionary: call.original_type.instance_methods).correct(call.method)).empty?
|
71
|
+
|
72
|
+
<<~MSG
|
73
|
+
|
74
|
+
There #{corrections.size == 1 ? "is" : "are"} also #{corrections.size} similar method#{"s" if corrections.size != 1} on #{call.original_type.name}.
|
75
|
+
|
76
|
+
Did you mean?
|
77
|
+
#{corrections.map { |c| " #{c}" }.join("\n")}
|
78
|
+
MSG
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class CreatesIdentifier
|
3
|
+
KEYWORDS = %w[__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield]
|
4
|
+
|
5
|
+
def create(s, default: "identifier", max_length: 24)
|
6
|
+
id = s.to_s.downcase
|
7
|
+
.gsub(/:0x[0-9a-f]+/, "") # Lazy attempt to wipe any Object:0x802beef identifiers
|
8
|
+
.gsub(/[^\w\s]/, "")
|
9
|
+
.gsub(/^\d+/, "")[0...max_length]
|
10
|
+
.strip
|
11
|
+
.gsub(/\s+/, "_") # snake_case
|
12
|
+
|
13
|
+
if id.empty?
|
14
|
+
default
|
15
|
+
else
|
16
|
+
unreserved(id, default)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def unreserved(id, default)
|
23
|
+
return id unless KEYWORDS.include?(id)
|
24
|
+
|
25
|
+
"#{id}_#{default}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,18 +1,22 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class StringifiesCall
|
3
|
-
def stringify(call)
|
4
|
-
"#{call.method}#{args_to_s(call)}#{blockify(call.block)}"
|
3
|
+
def stringify(call, anonymous_blocks: false, always_parens: false)
|
4
|
+
"#{call.method}#{args_to_s(call, parens: always_parens)}#{blockify(call.block, anonymous: anonymous_blocks)}"
|
5
5
|
end
|
6
6
|
|
7
7
|
private
|
8
8
|
|
9
|
-
def args_to_s(call)
|
10
|
-
|
9
|
+
def args_to_s(call, parens: true)
|
10
|
+
args_lists = [
|
11
11
|
argify(call.args),
|
12
12
|
kwargify(call.kwargs),
|
13
13
|
lambdafy(call.block)
|
14
|
-
].compact
|
14
|
+
].compact
|
15
|
+
|
16
|
+
if !args_lists.empty?
|
15
17
|
"(#{args_lists.join(", ")})"
|
18
|
+
elsif parens
|
19
|
+
"()"
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
@@ -31,9 +35,14 @@ module Mocktail
|
|
31
35
|
"&lambda[#{source_locationify(block)}]"
|
32
36
|
end
|
33
37
|
|
34
|
-
def blockify(block)
|
38
|
+
def blockify(block, anonymous:)
|
35
39
|
return unless block && !block.lambda?
|
36
|
-
|
40
|
+
|
41
|
+
if anonymous
|
42
|
+
" {…}"
|
43
|
+
else
|
44
|
+
" { Proc at #{source_locationify(block)} }"
|
45
|
+
end
|
37
46
|
end
|
38
47
|
|
39
48
|
def source_locationify(block)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class CleansBacktrace
|
3
|
+
BASE_PATH = (Pathname.new(__FILE__) + "../../..").to_s
|
4
|
+
|
5
|
+
def clean(error)
|
6
|
+
raise error
|
7
|
+
rescue => e
|
8
|
+
e.tap do |e|
|
9
|
+
e.set_backtrace(e.backtrace.drop_while { |frame|
|
10
|
+
frame.start_with?(BASE_PATH)
|
11
|
+
})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class ReconcilesArgsWithParams
|
3
|
+
def reconcile(signature)
|
4
|
+
args_match?(signature.positional_params, signature.positional_args) &&
|
5
|
+
kwargs_match?(signature.keyword_params, signature.keyword_args)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def args_match?(arg_params, args)
|
11
|
+
args.size >= arg_params.required.size &&
|
12
|
+
(arg_params.rest? || args.size <= arg_params.allowed.size)
|
13
|
+
end
|
14
|
+
|
15
|
+
def kwargs_match?(kwarg_params, kwargs)
|
16
|
+
kwarg_params.required.all? { |name| kwargs.key?(name) } &&
|
17
|
+
(kwarg_params.rest? || kwargs.keys.all? { |name| kwarg_params.allowed.include?(name) })
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class RecreatesMessage
|
3
|
+
def recreate(signature)
|
4
|
+
req_args = signature.positional_params.required.size
|
5
|
+
allowed_args = signature.positional_params.allowed.size
|
6
|
+
rest_args = signature.positional_params.rest?
|
7
|
+
req_kwargs = signature.keyword_params.required
|
8
|
+
|
9
|
+
if signature.positional_args.size < req_args || (!rest_args && signature.positional_args.size > allowed_args)
|
10
|
+
expected_desc = if rest_args
|
11
|
+
"#{req_args}+"
|
12
|
+
elsif allowed_args != req_args
|
13
|
+
"#{req_args}..#{allowed_args}"
|
14
|
+
else
|
15
|
+
req_args.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
"wrong number of arguments (given #{signature.positional_args.size}, expected #{expected_desc}#{"; required keyword#{"s" if req_kwargs.size > 1}: #{req_kwargs.join(", ")}" unless req_kwargs.empty?})"
|
19
|
+
|
20
|
+
elsif !(missing_kwargs = req_kwargs.reject { |name| signature.keyword_args.key?(name) }).empty?
|
21
|
+
"missing keyword#{"s" if missing_kwargs.size > 1}: #{missing_kwargs.map { |name| name.inspect }.join(", ")}"
|
22
|
+
elsif !(unknown_kwargs = signature.keyword_args.keys.reject { |name| signature.keyword_params.all.include?(name) }).empty?
|
23
|
+
"unknown keyword#{"s" if unknown_kwargs.size > 1}: #{unknown_kwargs.map { |name| name.inspect }.join(", ")}"
|
24
|
+
else
|
25
|
+
"unknown cause (this is probably a bug in Mocktail)"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class TransformsParams
|
3
|
+
def transform(dry_call)
|
4
|
+
params = dry_call.original_method.parameters
|
5
|
+
|
6
|
+
Signature.new(
|
7
|
+
positional_params: Params.new(
|
8
|
+
all: params.select { |type, _|
|
9
|
+
[:req, :opt, :rest].include?(type)
|
10
|
+
}.map { |_, name| name },
|
11
|
+
required: params.select { |t, _| t == :req }.map { |_, n| n },
|
12
|
+
optional: params.select { |t, _| t == :opt }.map { |_, n| n },
|
13
|
+
rest: params.find { |type, _| type == :rest } & [1]
|
14
|
+
),
|
15
|
+
positional_args: dry_call.args,
|
16
|
+
|
17
|
+
keyword_params: Params.new(
|
18
|
+
all: params.select { |type, _|
|
19
|
+
[:keyreq, :key, :keyrest].include?(type)
|
20
|
+
}.map { |_, name| name },
|
21
|
+
required: params.select { |t, _| t == :keyreq }.map { |_, n| n },
|
22
|
+
optional: params.select { |t, _| t == :key }.map { |_, n| n },
|
23
|
+
rest: params.find { |type, _| type == :keyrest } & [1]
|
24
|
+
),
|
25
|
+
keyword_args: dry_call.kwargs,
|
26
|
+
|
27
|
+
block_param: params.find { |type, _| type == :block } & [1],
|
28
|
+
block_arg: dry_call.block
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative "simulates_argument_error/transforms_params"
|
2
|
+
require_relative "simulates_argument_error/reconciles_args_with_params"
|
3
|
+
require_relative "simulates_argument_error/recreates_message"
|
4
|
+
require_relative "simulates_argument_error/cleans_backtrace"
|
5
|
+
require_relative "share/stringifies_call"
|
6
|
+
|
7
|
+
module Mocktail
|
8
|
+
class SimulatesArgumentError
|
9
|
+
def initialize
|
10
|
+
@transforms_params = TransformsParams.new
|
11
|
+
@reconciles_args_with_params = ReconcilesArgsWithParams.new
|
12
|
+
@recreates_message = RecreatesMessage.new
|
13
|
+
@cleans_backtrace = CleansBacktrace.new
|
14
|
+
@stringifies_call = StringifiesCall.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def simulate(dry_call)
|
18
|
+
signature = @transforms_params.transform(dry_call)
|
19
|
+
|
20
|
+
unless @reconciles_args_with_params.reconcile(signature)
|
21
|
+
@cleans_backtrace.clean(
|
22
|
+
ArgumentError.new([
|
23
|
+
@recreates_message.recreate(signature),
|
24
|
+
"[Mocktail call: `#{@stringifies_call.stringify(dry_call)}']"
|
25
|
+
].join(" "))
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -37,5 +37,17 @@ module Mocktail
|
|
37
37
|
def demonstration_in_progress?
|
38
38
|
@demonstration_in_progress
|
39
39
|
end
|
40
|
+
|
41
|
+
def double_for_instance(thing)
|
42
|
+
@doubles.find { |double| double.dry_instance == thing }
|
43
|
+
end
|
44
|
+
|
45
|
+
def stubbings_for_double(double)
|
46
|
+
@stubbings.select { |stubbing| stubbing.recording.double == double.dry_instance }
|
47
|
+
end
|
48
|
+
|
49
|
+
def calls_for_double(double)
|
50
|
+
@calls.select { |call| call.double == double.dry_instance }
|
51
|
+
end
|
40
52
|
end
|
41
53
|
end
|
@@ -1,11 +1,10 @@
|
|
1
1
|
module Mocktail
|
2
|
-
class Double
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
2
|
+
class Double < Struct.new(
|
3
|
+
:original_type,
|
4
|
+
:dry_type,
|
5
|
+
:dry_instance,
|
6
|
+
:dry_methods,
|
7
|
+
keyword_init: true
|
8
|
+
)
|
10
9
|
end
|
11
10
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class Explanation
|
3
|
+
attr_reader :reference, :message
|
4
|
+
|
5
|
+
def initialize(reference, message)
|
6
|
+
@reference = reference
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
self.class
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class NoExplanation < Explanation
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnsatisfiedStubExplanation < Explanation
|
19
|
+
end
|
20
|
+
|
21
|
+
class DoubleExplanation < Explanation
|
22
|
+
end
|
23
|
+
|
24
|
+
class ReplacedTypeExplanation < Explanation
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class Signature < Struct.new(
|
3
|
+
:positional_params,
|
4
|
+
:positional_args,
|
5
|
+
:keyword_params,
|
6
|
+
:keyword_args,
|
7
|
+
:block_param,
|
8
|
+
:block_arg,
|
9
|
+
keyword_init: true
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Params < Struct.new(
|
14
|
+
:all,
|
15
|
+
:required,
|
16
|
+
:optional,
|
17
|
+
:rest,
|
18
|
+
keyword_init: true
|
19
|
+
)
|
20
|
+
|
21
|
+
def initialize(**params)
|
22
|
+
super
|
23
|
+
self.all ||= []
|
24
|
+
self.required ||= []
|
25
|
+
self.optional ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def allowed
|
29
|
+
required + optional
|
30
|
+
end
|
31
|
+
|
32
|
+
def rest?
|
33
|
+
!!rest
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class StubReturnedNil < BasicObject
|
3
|
+
attr_reader :unsatisfied_stubbing
|
4
|
+
|
5
|
+
def initialize(unsatisfied_stubbing)
|
6
|
+
@unsatisfied_stubbing = unsatisfied_stubbing
|
7
|
+
end
|
8
|
+
|
9
|
+
def was_returned_by_unsatisfied_stub?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def tap
|
14
|
+
yield self
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(name, *args, **kwargs, &blk)
|
19
|
+
nil.send(name, *args, **kwargs, &blk)
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to_missing?(name, include_all = false)
|
23
|
+
nil.respond_to?(name, include_all)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|