mocktail 1.2.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +6 -5
- data/.gitignore +3 -0
- data/.standard.yml +8 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +98 -25
- data/README.md +18 -922
- data/Rakefile +0 -1
- data/bin/console +1 -2
- data/bin/tapioca +29 -0
- data/lib/mocktail/collects_calls.rb +2 -0
- data/lib/mocktail/debug.rb +13 -10
- data/lib/mocktail/dsl.rb +2 -0
- data/lib/mocktail/errors.rb +2 -0
- data/lib/mocktail/explains_nils.rb +2 -0
- data/lib/mocktail/explains_thing.rb +7 -4
- data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
- data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
- data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
- data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
- data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
- data/lib/mocktail/handles_dry_call.rb +2 -0
- data/lib/mocktail/handles_dry_new_call.rb +2 -0
- data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
- data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
- data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
- data/lib/mocktail/imitates_type/makes_double.rb +3 -0
- data/lib/mocktail/imitates_type.rb +3 -1
- data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
- data/lib/mocktail/initializes_mocktail.rb +5 -0
- data/lib/mocktail/matcher_presentation.rb +4 -2
- data/lib/mocktail/matchers/any.rb +4 -3
- data/lib/mocktail/matchers/base.rb +10 -2
- data/lib/mocktail/matchers/captor.rb +9 -0
- data/lib/mocktail/matchers/includes.rb +2 -0
- data/lib/mocktail/matchers/includes_hash.rb +9 -0
- data/lib/mocktail/matchers/includes_key.rb +9 -0
- data/lib/mocktail/matchers/includes_string.rb +9 -0
- data/lib/mocktail/matchers/is_a.rb +2 -0
- data/lib/mocktail/matchers/matches.rb +2 -0
- data/lib/mocktail/matchers/not.rb +2 -0
- data/lib/mocktail/matchers/numeric.rb +5 -4
- data/lib/mocktail/matchers/that.rb +2 -0
- data/lib/mocktail/matchers.rb +3 -0
- data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
- data/lib/mocktail/records_demonstration.rb +2 -0
- data/lib/mocktail/registers_matcher.rb +8 -3
- data/lib/mocktail/registers_stubbing.rb +2 -0
- data/lib/mocktail/replaces_next.rb +7 -1
- data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
- data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
- data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
- data/lib/mocktail/replaces_type.rb +6 -0
- data/lib/mocktail/resets_state.rb +2 -0
- data/lib/mocktail/share/bind.rb +7 -5
- data/lib/mocktail/share/cleans_backtrace.rb +3 -5
- data/lib/mocktail/share/creates_identifier.rb +16 -9
- data/lib/mocktail/share/determines_matching_calls.rb +4 -2
- data/lib/mocktail/share/stringifies_call.rb +6 -2
- data/lib/mocktail/share/stringifies_method_name.rb +3 -1
- data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
- data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
- data/lib/mocktail/simulates_argument_error/transforms_params.rb +15 -8
- data/lib/mocktail/simulates_argument_error.rb +2 -0
- data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
- data/lib/mocktail/sorbet/mocktail/debug.rb +54 -0
- data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
- data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
- data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
- data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
- data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
- data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
- data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
- data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
- data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
- data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
- data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
- data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
- data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
- data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
- data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
- data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
- data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
- data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
- data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
- data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
- data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
- data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
- data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
- data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
- data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
- data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +85 -0
- data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
- data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
- data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
- data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
- data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
- data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
- data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
- data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
- data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
- data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
- data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
- data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
- data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
- data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
- data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
- data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
- data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
- data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
- data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
- data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
- data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
- data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
- data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
- data/lib/mocktail/sorbet/mocktail.rb +154 -0
- data/lib/mocktail/sorbet.rb +1 -0
- data/lib/mocktail/stringifies_method_signature.rb +2 -0
- data/lib/mocktail/typed.rb +3 -0
- data/lib/mocktail/value/cabinet.rb +8 -1
- data/lib/mocktail/value/call.rb +44 -12
- data/lib/mocktail/value/demo_config.rb +6 -7
- data/lib/mocktail/value/double.rb +6 -7
- data/lib/mocktail/value/double_data.rb +11 -7
- data/lib/mocktail/value/explanation.rb +28 -3
- data/lib/mocktail/value/explanation_data.rb +14 -0
- data/lib/mocktail/value/fake_method_data.rb +7 -6
- data/lib/mocktail/value/matcher_registry.rb +2 -0
- data/lib/mocktail/value/no_explanation_data.rb +16 -0
- data/lib/mocktail/value/signature.rb +19 -27
- data/lib/mocktail/value/stubbing.rb +11 -12
- data/lib/mocktail/value/top_shelf.rb +5 -0
- data/lib/mocktail/value/type_replacement.rb +7 -8
- data/lib/mocktail/value/type_replacement_data.rb +10 -7
- data/lib/mocktail/value/unsatisfying_call.rb +5 -6
- data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
- data/lib/mocktail/value.rb +5 -2
- data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
- data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
- data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
- data/lib/mocktail/verifies_call.rb +3 -0
- data/lib/mocktail/version.rb +8 -1
- data/lib/mocktail.rb +46 -5
- data/mocktail.gemspec +8 -4
- data/rbi/mocktail-pregenerated.rbi +1865 -0
- data/rbi/mocktail.rbi +77 -0
- data/rbi/sorbet-runtime.rbi +29 -0
- data/spoom_report.html +1248 -0
- metadata +130 -3
@@ -1,16 +1,17 @@
|
|
1
1
|
module Mocktail::Matchers
|
2
2
|
class Numeric < Base
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def self.matcher_name
|
4
6
|
:numeric
|
5
7
|
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
def initialize # standard:disable Style/RedundantInitialize
|
9
|
+
def initialize
|
10
|
+
# Empty initialize is necessary b/c Base default expects an argument
|
10
11
|
end
|
11
12
|
|
12
13
|
def match?(actual)
|
13
|
-
|
14
|
+
actual.is_a?(::Numeric)
|
14
15
|
end
|
15
16
|
|
16
17
|
def inspect
|
data/lib/mocktail/matchers.rb
CHANGED
@@ -7,6 +7,9 @@ require_relative "matchers/base"
|
|
7
7
|
require_relative "matchers/any"
|
8
8
|
require_relative "matchers/captor"
|
9
9
|
require_relative "matchers/includes"
|
10
|
+
require_relative "matchers/includes_string"
|
11
|
+
require_relative "matchers/includes_hash"
|
12
|
+
require_relative "matchers/includes_key"
|
10
13
|
require_relative "matchers/is_a"
|
11
14
|
require_relative "matchers/matches"
|
12
15
|
require_relative "matchers/not"
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class RegistersMatcher
|
3
|
+
extend T::Sig
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@grabs_original_method_parameters = GrabsOriginalMethodParameters.new
|
7
|
+
end
|
8
|
+
|
3
9
|
def register(matcher_type)
|
4
10
|
if invalid_type?(matcher_type)
|
5
11
|
raise InvalidMatcherError.new <<~MSG.tr("\n", " ")
|
@@ -32,12 +38,11 @@ module Mocktail
|
|
32
38
|
return true unless matcher_type.respond_to?(:matcher_name)
|
33
39
|
name = matcher_type.matcher_name
|
34
40
|
|
35
|
-
!
|
36
|
-
name.to_sym.inspect.start_with?(":\"")
|
41
|
+
!name.respond_to?(:to_sym) || name.to_sym.inspect.start_with?(":\"")
|
37
42
|
end
|
38
43
|
|
39
44
|
def invalid_match?(matcher_type)
|
40
|
-
params = matcher_type.instance_method(:match?)
|
45
|
+
params = @grabs_original_method_parameters.grab(matcher_type.instance_method(:match?))
|
41
46
|
params.size > 1 || ![:req, :opt].include?(params.first[0])
|
42
47
|
rescue NameError
|
43
48
|
true
|
@@ -1,11 +1,17 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class ReplacesNext
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def initialize
|
4
6
|
@top_shelf = TopShelf.instance
|
5
7
|
@redefines_new = RedefinesNew.new
|
6
8
|
@imitates_type = ImitatesType.new
|
7
9
|
end
|
8
10
|
|
11
|
+
def replace_once(type)
|
12
|
+
replace(type, 1).fetch(0)
|
13
|
+
end
|
14
|
+
|
9
15
|
def replace(type, count)
|
10
16
|
raise UnsupportedMocktail.new("Mocktail.of_next() only supports classes") unless type.is_a?(Class)
|
11
17
|
|
@@ -30,7 +36,7 @@ module Mocktail
|
|
30
36
|
}
|
31
37
|
end
|
32
38
|
|
33
|
-
|
39
|
+
mocktails
|
34
40
|
end
|
35
41
|
end
|
36
42
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class RedefinesNew
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def initialize
|
4
6
|
@handles_dry_new_call = HandlesDryNewCall.new
|
5
7
|
end
|
@@ -13,7 +15,7 @@ module Mocktail
|
|
13
15
|
handles_dry_new_call = @handles_dry_new_call
|
14
16
|
type.define_singleton_method :new, ->(*args, **kwargs, &block) {
|
15
17
|
if TopShelf.instance.new_replaced?(type) ||
|
16
|
-
TopShelf.instance.of_next_registered?(type)
|
18
|
+
(type.is_a?(Class) && TopShelf.instance.of_next_registered?(type))
|
17
19
|
handles_dry_new_call.handle(type, args, kwargs, block)
|
18
20
|
else
|
19
21
|
type_replacement.original_new.call(*args, **kwargs, &block)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class RedefinesSingletonMethods
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def initialize
|
4
6
|
@handles_dry_call = HandlesDryCall.new
|
5
7
|
end
|
@@ -10,11 +12,11 @@ module Mocktail
|
|
10
12
|
|
11
13
|
type_replacement.original_methods = type.singleton_methods.map { |name|
|
12
14
|
type.method(name)
|
13
|
-
} - [type_replacement.replacement_new]
|
15
|
+
}.reject { |method| sorbet_method_hook?(method) } - [type_replacement.replacement_new]
|
14
16
|
|
15
17
|
declare_singleton_method_missing_errors!(type)
|
16
18
|
handles_dry_call = @handles_dry_call
|
17
|
-
type_replacement.replacement_methods = type_replacement.original_methods
|
19
|
+
type_replacement.replacement_methods = type_replacement.original_methods&.map { |original_method|
|
18
20
|
type.singleton_class.send(:undef_method, original_method.name)
|
19
21
|
type.define_singleton_method original_method.name, ->(*args, **kwargs, &block) {
|
20
22
|
if TopShelf.instance.singleton_methods_replaced?(type)
|
@@ -58,5 +60,15 @@ module Mocktail
|
|
58
60
|
)
|
59
61
|
}
|
60
62
|
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def sorbet_method_hook?(method)
|
67
|
+
[
|
68
|
+
T::Sig,
|
69
|
+
T::Private::Methods::MethodHooks,
|
70
|
+
T::Private::Methods::SingletonMethodHooks
|
71
|
+
].include?(method.owner)
|
72
|
+
end
|
61
73
|
end
|
62
74
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class RunsSorbetSigBlocksBeforeReplacement
|
3
|
+
extend T::Sig
|
4
|
+
|
5
|
+
# This is necessary because when Sorbet runs a sig block of a singleton
|
6
|
+
# method, it has the net effect of unwrapping/redefining the method. If
|
7
|
+
# we try to use Mocktail.replace(Foo) and Foo.bar has a Sorbet sig block,
|
8
|
+
# then we'll end up with three "versions" of the same method and no way
|
9
|
+
# to keep straight which one == which:
|
10
|
+
#
|
11
|
+
# A - Foo.bar, as defined in the original class
|
12
|
+
# B - Foo.bar, as redefined by RedefinesSingletonMethods
|
13
|
+
# C - Foo.bar, as wrapped by sorbet-runtime
|
14
|
+
#
|
15
|
+
# Initially, Foo.method(:bar) would == C, but after the type
|
16
|
+
# replacement, it would == B (with a reference back to C as the original),
|
17
|
+
# but after handling a single dry call, our invocation of
|
18
|
+
# GrabsOriginalMethodParameters.grab(Foo.method(:bar)) would invoke the
|
19
|
+
# Sorbet `sig` block, which has the net effect of redefining the method back
|
20
|
+
# to A.
|
21
|
+
#
|
22
|
+
# It's very fun and confusing and a great time.
|
23
|
+
|
24
|
+
def run(type)
|
25
|
+
return unless defined?(T::Private::Methods)
|
26
|
+
|
27
|
+
type.singleton_methods.each do |method_name|
|
28
|
+
method = type.method(method_name)
|
29
|
+
|
30
|
+
# Again: calling this for the side effect of running the sig block
|
31
|
+
#
|
32
|
+
# https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/private/methods/_methods.rb#L111
|
33
|
+
T::Private::Methods.signature_for_method(method)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
require_relative "replaces_type/redefines_new"
|
2
2
|
require_relative "replaces_type/redefines_singleton_methods"
|
3
|
+
require_relative "replaces_type/runs_sorbet_sig_blocks_before_replacement"
|
3
4
|
|
4
5
|
module Mocktail
|
5
6
|
class ReplacesType
|
7
|
+
extend T::Sig
|
8
|
+
|
6
9
|
def initialize
|
7
10
|
@top_shelf = TopShelf.instance
|
11
|
+
@runs_sorbet_sig_blocks_before_replacement = RunsSorbetSigBlocksBeforeReplacement.new
|
8
12
|
@redefines_new = RedefinesNew.new
|
9
13
|
@redefines_singleton_methods = RedefinesSingletonMethods.new
|
10
14
|
end
|
@@ -14,6 +18,8 @@ module Mocktail
|
|
14
18
|
raise UnsupportedMocktail.new("Mocktail.replace() only supports classes and modules")
|
15
19
|
end
|
16
20
|
|
21
|
+
@runs_sorbet_sig_blocks_before_replacement.run(type)
|
22
|
+
|
17
23
|
if type.is_a?(Class)
|
18
24
|
@top_shelf.register_new_replacement!(type)
|
19
25
|
@redefines_new.redefine(type)
|
data/lib/mocktail/share/bind.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
module Mocktail
|
2
2
|
module Bind
|
3
|
-
|
3
|
+
# sig intentionally omitted, because the wrapper will cause infinite recursion if certain methods are mocked
|
4
|
+
def self.call(mock, method_name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
|
4
5
|
if Mocktail.cabinet.double_for_instance(mock)
|
5
|
-
Object.instance_method(method_name).bind_call(mock, *args, **kwargs, &blk)
|
6
|
-
elsif (
|
6
|
+
Object.instance_method(method_name).bind_call(mock, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
|
7
|
+
elsif (mock.is_a?(Module) || mock.is_a?(Class)) &&
|
8
|
+
(type_replacement = TopShelf.instance.type_replacement_if_exists_for(mock)) &&
|
7
9
|
(og_method = type_replacement.original_methods&.find { |m| m.name == method_name })
|
8
|
-
og_method.call(*args, **kwargs, &blk)
|
10
|
+
og_method.call(*args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
|
9
11
|
else
|
10
|
-
mock.__send__(method_name, *args, **kwargs, &blk)
|
12
|
+
mock.__send__(method_name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -1,15 +1,13 @@
|
|
1
|
-
require "pathname"
|
2
|
-
|
3
1
|
module Mocktail
|
4
2
|
class CleansBacktrace
|
5
|
-
|
3
|
+
extend T::Sig
|
6
4
|
|
7
5
|
def clean(error)
|
8
6
|
raise error
|
9
|
-
rescue => e
|
7
|
+
rescue error.class => e
|
10
8
|
e.tap do |e|
|
11
9
|
e.set_backtrace(e.backtrace.drop_while { |frame|
|
12
|
-
frame.start_with?(BASE_PATH)
|
10
|
+
frame.start_with?(BASE_PATH, BASE_PATH) || frame.match?(/[\\|\/]sorbet-runtime.*[\\|\/]lib[\\|\/]types[\\|\/]private/)
|
13
11
|
})
|
14
12
|
end
|
15
13
|
end
|
@@ -1,19 +1,26 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class CreatesIdentifier
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
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
6
|
|
5
7
|
def create(s, default: "identifier", max_length: 24)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
case s
|
9
|
+
when Kernel
|
10
|
+
id = (s.to_s.downcase
|
11
|
+
.gsub(/:0x[0-9a-f]+/, "") # Lazy attempt to wipe any Object:0x802beef identifiers
|
12
|
+
.gsub(/[^\w\s]/, "")
|
13
|
+
.gsub(/^\d+/, "")[0...max_length] || "")
|
14
|
+
.strip
|
15
|
+
.gsub(/\s+/, "_") # snake_case
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
if id.empty?
|
18
|
+
default
|
19
|
+
else
|
20
|
+
unreserved(id, default)
|
21
|
+
end
|
15
22
|
else
|
16
|
-
|
23
|
+
default
|
17
24
|
end
|
18
25
|
end
|
19
26
|
|
@@ -2,6 +2,8 @@ require_relative "bind"
|
|
2
2
|
|
3
3
|
module Mocktail
|
4
4
|
class DeterminesMatchingCalls
|
5
|
+
extend T::Sig
|
6
|
+
|
5
7
|
def determine(real_call, demo_call, demo_config)
|
6
8
|
Bind.call(real_call.double, :==, demo_call.double) &&
|
7
9
|
real_call.method == demo_call.method &&
|
@@ -40,7 +42,7 @@ module Mocktail
|
|
40
42
|
end
|
41
43
|
|
42
44
|
def blocks_match?(real_block, demo_block, ignore_block)
|
43
|
-
ignore_block ||
|
45
|
+
!!(ignore_block ||
|
44
46
|
(real_block.nil? && demo_block.nil?) ||
|
45
47
|
(
|
46
48
|
real_block && demo_block &&
|
@@ -48,7 +50,7 @@ module Mocktail
|
|
48
50
|
demo_block == real_block ||
|
49
51
|
demo_block.call(real_block)
|
50
52
|
)
|
51
|
-
)
|
53
|
+
))
|
52
54
|
end
|
53
55
|
|
54
56
|
def match?(real_arg, demo_arg)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class StringifiesCall
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def stringify(call, anonymous_blocks: false, always_parens: false)
|
4
6
|
"#{call.method}#{args_to_s(call, parens: always_parens)}#{blockify(call.block, anonymous: anonymous_blocks)}"
|
5
7
|
end
|
@@ -31,16 +33,18 @@ module Mocktail
|
|
31
33
|
"(#{args_lists.join(", ")})"
|
32
34
|
elsif parens
|
33
35
|
"()"
|
36
|
+
else
|
37
|
+
""
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
37
41
|
def argify(args)
|
38
|
-
return unless
|
42
|
+
return unless !args.empty?
|
39
43
|
args.map(&:inspect).join(", ")
|
40
44
|
end
|
41
45
|
|
42
46
|
def kwargify(kwargs)
|
43
|
-
return unless
|
47
|
+
return unless !kwargs.empty?
|
44
48
|
kwargs.map { |key, val| "#{key}: #{val.inspect}" }.join(", ")
|
45
49
|
end
|
46
50
|
|
@@ -2,16 +2,23 @@ require_relative "../share/bind"
|
|
2
2
|
|
3
3
|
module Mocktail
|
4
4
|
class TransformsParams
|
5
|
-
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@grabs_original_method_parameters = GrabsOriginalMethodParameters.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def transform(dry_call, params: nil)
|
12
|
+
params ||= @grabs_original_method_parameters.grab(dry_call.original_method)
|
6
13
|
params = name_unnamed_params(params)
|
7
14
|
|
8
15
|
Signature.new(
|
9
16
|
positional_params: Params.new(
|
10
17
|
all: params.select { |t, _|
|
11
18
|
[:req, :opt, :rest].any? { |param_type| Bind.call(t, :==, param_type) }
|
12
|
-
}.map { |
|
13
|
-
required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |
|
14
|
-
optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |
|
19
|
+
}.map { |pair| pair.fetch(1) },
|
20
|
+
required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |pair| pair.fetch(1) },
|
21
|
+
optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |pair| pair.fetch(1) },
|
15
22
|
rest: params.find { |t, _| Bind.call(t, :==, :rest) }&.last
|
16
23
|
),
|
17
24
|
positional_args: dry_call.args,
|
@@ -19,9 +26,9 @@ module Mocktail
|
|
19
26
|
keyword_params: Params.new(
|
20
27
|
all: params.select { |type, _|
|
21
28
|
[:keyreq, :key, :keyrest].include?(type)
|
22
|
-
}.map { |
|
23
|
-
required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |
|
24
|
-
optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |
|
29
|
+
}.map { |pair| pair.fetch(1) },
|
30
|
+
required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |pair| pair.fetch(1) },
|
31
|
+
optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |pair| pair.fetch(1) },
|
25
32
|
rest: params.find { |t, _| Bind.call(t, :==, :keyrest) }&.last
|
26
33
|
),
|
27
34
|
keyword_args: dry_call.kwargs,
|
@@ -36,7 +43,7 @@ module Mocktail
|
|
36
43
|
def name_unnamed_params(params)
|
37
44
|
params.map.with_index { |param, i|
|
38
45
|
if param.size == 1
|
39
|
-
param + ["unnamed_arg_#{i + 1}"]
|
46
|
+
param + ["unnamed_arg_#{i + 1}".to_sym]
|
40
47
|
else
|
41
48
|
param
|
42
49
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class CollectsCalls
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(double: Object, method_name: T.nilable(Symbol)).returns(T::Array[Call]) }
|
8
|
+
def collect(double, method_name)
|
9
|
+
calls = ExplainsThing.new.explain(double).reference.calls
|
10
|
+
|
11
|
+
if method_name.nil?
|
12
|
+
calls
|
13
|
+
else
|
14
|
+
calls.select { |call| call.method.to_s == method_name.to_s }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
module Debug
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
# It would be easy and bad for the mocktail lib to call something like
|
8
|
+
#
|
9
|
+
# double == other_double
|
10
|
+
#
|
11
|
+
# But if it's a double, that means anyone who stubs that method could change
|
12
|
+
# the internal behavior of the library in unexpected ways (as happened here:
|
13
|
+
# https://github.com/testdouble/mocktail/issues/7 )
|
14
|
+
#
|
15
|
+
# For that reason when we run our tests, we also want to blow up if this
|
16
|
+
# happens unintentionally. This works in conjunction with the test
|
17
|
+
# MockingMethodfulClassesTest, because it mocks every defined method on the
|
18
|
+
# mocked BasicObject
|
19
|
+
sig { void }
|
20
|
+
def self.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
|
21
|
+
return unless ENV["MOCKTAIL_DEBUG_ACCIDENTAL_INTERNAL_MOCK_CALLS"]
|
22
|
+
raise Mocktail::Error
|
23
|
+
rescue Mocktail::Error => e
|
24
|
+
base_path = Pathname.new(__FILE__).dirname.to_s
|
25
|
+
backtrace_minus_this_and_whoever_called_this = e.backtrace&.[](2..)
|
26
|
+
internal_call_sites = backtrace_minus_this_and_whoever_called_this&.take_while { |call_site|
|
27
|
+
# the "in `block" is very confusing but necessary to include lines after
|
28
|
+
# a stubs { blah.foo }.with { … } call, since that's when most of the
|
29
|
+
# good stuff happens
|
30
|
+
call_site.start_with?(base_path) || call_site.include?("in `block")
|
31
|
+
}&.reject { |call_site| call_site.include?("in `block") } || []
|
32
|
+
|
33
|
+
approved_call_sites = [
|
34
|
+
/fulfills_stubbing.rb:(16|20)/,
|
35
|
+
/validates_arguments.rb:(18|23)/,
|
36
|
+
/validates_arguments.rb:(21|26)/
|
37
|
+
]
|
38
|
+
if internal_call_sites.any? && approved_call_sites.none? { |approved_call_site|
|
39
|
+
internal_call_sites.first&.match?(approved_call_site)
|
40
|
+
}
|
41
|
+
raise Error.new <<~MSG
|
42
|
+
Unauthorized internal call of a mock internally by Mocktail itself:
|
43
|
+
|
44
|
+
#{internal_call_sites.first}
|
45
|
+
|
46
|
+
Offending call's complete stack trace:
|
47
|
+
|
48
|
+
#{backtrace_minus_this_and_whoever_called_this&.join("\n")}
|
49
|
+
==END OFFENDING TRACE==
|
50
|
+
MSG
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
module DSL
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig {
|
8
|
+
type_parameters(:T)
|
9
|
+
.params(
|
10
|
+
ignore_block: T::Boolean,
|
11
|
+
ignore_extra_args: T::Boolean,
|
12
|
+
ignore_arity: T::Boolean,
|
13
|
+
times: T.nilable(Integer),
|
14
|
+
demo: T.proc.params(matchers: Mocktail::MatcherPresentation).returns(T.type_parameter(:T))
|
15
|
+
)
|
16
|
+
.returns(Mocktail::Stubbing[T.type_parameter(:T)])
|
17
|
+
}
|
18
|
+
def stubs(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
|
19
|
+
RegistersStubbing.new.register(demo, DemoConfig.new(
|
20
|
+
ignore_block: ignore_block,
|
21
|
+
ignore_extra_args: ignore_extra_args,
|
22
|
+
ignore_arity: ignore_arity,
|
23
|
+
times: times
|
24
|
+
))
|
25
|
+
end
|
26
|
+
|
27
|
+
sig {
|
28
|
+
type_parameters(:T)
|
29
|
+
.params(
|
30
|
+
ignore_block: T::Boolean,
|
31
|
+
ignore_extra_args: T::Boolean,
|
32
|
+
ignore_arity: T::Boolean,
|
33
|
+
times: T.nilable(Integer),
|
34
|
+
demo: T.proc.params(matchers: Mocktail::MatcherPresentation).void
|
35
|
+
).void
|
36
|
+
}
|
37
|
+
def verify(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
|
38
|
+
VerifiesCall.new.verify(demo, DemoConfig.new(
|
39
|
+
ignore_block: ignore_block,
|
40
|
+
ignore_extra_args: ignore_extra_args,
|
41
|
+
ignore_arity: ignore_arity,
|
42
|
+
times: times
|
43
|
+
))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class UnexpectedError < Error; end
|
7
|
+
|
8
|
+
class UnsupportedMocktail < Error; end
|
9
|
+
|
10
|
+
class MissingDemonstrationError < Error; end
|
11
|
+
|
12
|
+
class AmbiguousDemonstrationError < Error; end
|
13
|
+
|
14
|
+
class InvalidMatcherError < Error; end
|
15
|
+
|
16
|
+
class VerificationError < Error; end
|
17
|
+
|
18
|
+
class TypeCheckingError < Error; end
|
19
|
+
end
|