mocktail 1.2.3 → 2.0.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 +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
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/bin/tapioca
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'tapioca' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("tapioca", "tapioca")
|
data/lib/mocktail/debug.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Mocktail
|
2
2
|
module Debug
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
# It would be easy and bad for the mocktail lib to call something like
|
4
6
|
#
|
5
7
|
# double == other_double
|
@@ -12,26 +14,27 @@ module Mocktail
|
|
12
14
|
# happens unintentionally. This works in conjunction with the test
|
13
15
|
# MockingMethodfulClassesTest, because it mocks every defined method on the
|
14
16
|
# mocked BasicObject
|
17
|
+
|
15
18
|
def self.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
|
16
19
|
return unless ENV["MOCKTAIL_DEBUG_ACCIDENTAL_INTERNAL_MOCK_CALLS"]
|
17
|
-
raise
|
18
|
-
rescue => e
|
20
|
+
raise Mocktail::Error
|
21
|
+
rescue Mocktail::Error => e
|
19
22
|
base_path = Pathname.new(__FILE__).dirname.to_s
|
20
|
-
backtrace_minus_this_and_whoever_called_this = e.backtrace[2..
|
21
|
-
internal_call_sites = backtrace_minus_this_and_whoever_called_this
|
23
|
+
backtrace_minus_this_and_whoever_called_this = e.backtrace&.[](2..)
|
24
|
+
internal_call_sites = backtrace_minus_this_and_whoever_called_this&.take_while { |call_site|
|
22
25
|
# the "in `block" is very confusing but necessary to include lines after
|
23
26
|
# a stubs { blah.foo }.with { … } call, since that's when most of the
|
24
27
|
# good stuff happens
|
25
28
|
call_site.start_with?(base_path) || call_site.include?("in `block")
|
26
|
-
}
|
29
|
+
}&.reject { |call_site| call_site.include?("in `block") } || []
|
27
30
|
|
28
31
|
approved_call_sites = [
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
/fulfills_stubbing.rb:(16|20)/,
|
33
|
+
/validates_arguments.rb:(18|23)/,
|
34
|
+
/validates_arguments.rb:(21|26)/
|
32
35
|
]
|
33
36
|
if internal_call_sites.any? && approved_call_sites.none? { |approved_call_site|
|
34
|
-
internal_call_sites.first
|
37
|
+
internal_call_sites.first&.match?(approved_call_site)
|
35
38
|
}
|
36
39
|
raise Error.new <<~MSG
|
37
40
|
Unauthorized internal call of a mock internally by Mocktail itself:
|
@@ -40,7 +43,7 @@ module Mocktail
|
|
40
43
|
|
41
44
|
Offending call's complete stack trace:
|
42
45
|
|
43
|
-
#{backtrace_minus_this_and_whoever_called_this
|
46
|
+
#{backtrace_minus_this_and_whoever_called_this&.join("\n")}
|
44
47
|
==END OFFENDING TRACE==
|
45
48
|
MSG
|
46
49
|
end
|
data/lib/mocktail/dsl.rb
CHANGED
data/lib/mocktail/errors.rb
CHANGED
@@ -3,6 +3,8 @@ require_relative "share/stringifies_call"
|
|
3
3
|
|
4
4
|
module Mocktail
|
5
5
|
class ExplainsThing
|
6
|
+
extend T::Sig
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
@stringifies_method_name = StringifiesMethodName.new
|
8
10
|
@stringifies_call = StringifiesCall.new
|
@@ -11,7 +13,8 @@ module Mocktail
|
|
11
13
|
def explain(thing)
|
12
14
|
if (double = Mocktail.cabinet.double_for_instance(thing))
|
13
15
|
double_explanation(double)
|
14
|
-
elsif (
|
16
|
+
elsif (thing.is_a?(Module) || thing.is_a?(Class)) &&
|
17
|
+
(type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
|
15
18
|
replaced_type_explanation(type_replacement)
|
16
19
|
elsif (fake_method_explanation = fake_method_explanation_for(thing))
|
17
20
|
fake_method_explanation
|
@@ -67,7 +70,7 @@ module Mocktail
|
|
67
70
|
def data_for_type_replacement(type_replacement)
|
68
71
|
TypeReplacementData.new(
|
69
72
|
type: type_replacement.type,
|
70
|
-
replaced_method_names: type_replacement.replacement_methods
|
73
|
+
replaced_method_names: type_replacement.replacement_methods&.map(&:name)&.sort || [],
|
71
74
|
calls: Mocktail.cabinet.calls.select { |call|
|
72
75
|
call.double == type_replacement.type
|
73
76
|
},
|
@@ -81,7 +84,7 @@ module Mocktail
|
|
81
84
|
type_replacement_data = data_for_type_replacement(type_replacement)
|
82
85
|
|
83
86
|
ReplacedTypeExplanation.new(type_replacement_data, <<~MSG)
|
84
|
-
`#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its
|
87
|
+
`#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its methods faked.
|
85
88
|
|
86
89
|
It has these mocked methods:
|
87
90
|
#{type_replacement_data.replaced_method_names.map { |method| " - #{method}" }.join("\n")}
|
@@ -116,7 +119,7 @@ module Mocktail
|
|
116
119
|
end
|
117
120
|
|
118
121
|
def no_explanation(thing)
|
119
|
-
NoExplanation.new(thing,
|
122
|
+
NoExplanation.new(NoExplanationData.new(thing: thing),
|
120
123
|
"Unfortunately, Mocktail doesn't know what this thing is: #{thing.inspect}")
|
121
124
|
end
|
122
125
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class GrabsOriginalMethodParameters
|
3
|
+
extend T::Sig
|
4
|
+
|
5
|
+
# Sorbet wraps the original method in a sig wrapper, so we need to unwrap it.
|
6
|
+
# The value returned from `owner.instance_method(method_name)` does not have
|
7
|
+
# the real parameters values available, as they'll have been erased
|
8
|
+
#
|
9
|
+
# If the method isn't wrapped by Sorbet, this will return the #instance_method,
|
10
|
+
# per usual
|
11
|
+
|
12
|
+
def grab(method)
|
13
|
+
return [] unless method
|
14
|
+
|
15
|
+
if (wrapped_method = sorbet_wrapped_method(method))
|
16
|
+
wrapped_method.parameters
|
17
|
+
else
|
18
|
+
method.parameters
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def sorbet_wrapped_method(method)
|
25
|
+
return unless defined?(::T::Private::Methods)
|
26
|
+
|
27
|
+
T::Private::Methods.signature_for_method(method)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,6 +3,8 @@ require_relative "../../share/bind"
|
|
3
3
|
|
4
4
|
module Mocktail
|
5
5
|
class DescribesUnsatisfiedStubbing
|
6
|
+
extend T::Sig
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
@cleans_backtrace = CleansBacktrace.new
|
8
10
|
end
|
@@ -14,7 +16,7 @@ module Mocktail
|
|
14
16
|
Bind.call(dry_call.double, :==, stubbing.recording.double) &&
|
15
17
|
dry_call.method == stubbing.recording.method
|
16
18
|
},
|
17
|
-
backtrace: @cleans_backtrace.clean(Error.new).backtrace
|
19
|
+
backtrace: @cleans_backtrace.clean(Error.new).backtrace || []
|
18
20
|
)
|
19
21
|
end
|
20
22
|
end
|
@@ -2,14 +2,18 @@ require_relative "../../share/determines_matching_calls"
|
|
2
2
|
|
3
3
|
module Mocktail
|
4
4
|
class FindsSatisfaction
|
5
|
+
extend T::Sig
|
6
|
+
|
5
7
|
def initialize
|
6
8
|
@determines_matching_calls = DeterminesMatchingCalls.new
|
7
9
|
end
|
8
10
|
|
9
11
|
def find(dry_call)
|
10
12
|
Mocktail.cabinet.stubbings.reverse.find { |stubbing|
|
13
|
+
demo_config_times = stubbing.demo_config.times
|
14
|
+
|
11
15
|
@determines_matching_calls.determine(dry_call, stubbing.recording, stubbing.demo_config) &&
|
12
|
-
(
|
16
|
+
(demo_config_times.nil? || demo_config_times > stubbing.satisfaction_count)
|
13
17
|
}
|
14
18
|
end
|
15
19
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class ValidatesArguments
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def self.disable!
|
4
6
|
Thread.current[:mocktail_arity_validation_disabled] = true
|
5
7
|
end
|
@@ -9,16 +11,16 @@ module Mocktail
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def self.disabled?
|
12
|
-
Thread.current[:mocktail_arity_validation_disabled]
|
14
|
+
!!Thread.current[:mocktail_arity_validation_disabled]
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.optional(disable, &blk)
|
16
18
|
return blk.call unless disable
|
17
19
|
|
18
20
|
disable!
|
19
|
-
blk.call
|
20
|
-
|
21
|
-
|
21
|
+
ret = blk.call
|
22
|
+
enable!
|
23
|
+
ret
|
22
24
|
end
|
23
25
|
|
24
26
|
def initialize
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class ReconstructsCall
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def reconstruct(double:, call_binding:, default_args:, dry_class:, type:, method:, original_method:, signature:)
|
4
6
|
Call.new(
|
5
7
|
singleton: false,
|
@@ -37,7 +39,8 @@ module Mocktail
|
|
37
39
|
def non_default_args(params, default_args)
|
38
40
|
named_args = params.allowed
|
39
41
|
.reject { |p| default_args&.key?(p) }
|
40
|
-
|
42
|
+
rest_param = params.rest
|
43
|
+
rest_arg = if rest_param && !default_args&.key?(rest_param)
|
41
44
|
params.rest
|
42
45
|
end
|
43
46
|
|
@@ -2,60 +2,72 @@ require_relative "declares_dry_class/reconstructs_call"
|
|
2
2
|
|
3
3
|
module Mocktail
|
4
4
|
class DeclaresDryClass
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
DEFAULT_ANCESTORS = Class.new(Object).ancestors[1..]
|
8
|
+
|
5
9
|
def initialize
|
6
10
|
@raises_neato_no_method_error = RaisesNeatoNoMethodError.new
|
7
11
|
@transforms_params = TransformsParams.new
|
8
12
|
@stringifies_method_signature = StringifiesMethodSignature.new
|
13
|
+
@grabs_original_method_parameters = GrabsOriginalMethodParameters.new
|
9
14
|
end
|
10
15
|
|
11
16
|
def declare(type, instance_methods)
|
12
17
|
dry_class = Class.new(Object) {
|
13
|
-
include type if type.
|
18
|
+
include type if type.is_a?(Module) && !type.is_a?(Class)
|
14
19
|
|
15
|
-
|
20
|
+
define_method :initialize do |*args, **kwargs, &blk|
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
[:is_a?, :kind_of?].each do |method_name|
|
24
|
+
define_method method_name, ->(thing) {
|
25
|
+
# Mocktails extend from Object, so share the same ancestors, plus the passed type
|
26
|
+
[type, *DEFAULT_ANCESTORS].include?(thing)
|
27
|
+
}
|
28
|
+
end
|
22
29
|
|
23
|
-
if type.
|
30
|
+
if type.is_a?(Class)
|
24
31
|
define_method :instance_of?, ->(thing) {
|
25
32
|
type == thing
|
26
33
|
}
|
27
34
|
end
|
28
35
|
}
|
29
36
|
|
30
|
-
|
31
|
-
|
32
|
-
#
|
37
|
+
add_more_methods!(dry_class, type, instance_methods)
|
38
|
+
|
39
|
+
dry_class # This is all fake! That's the whole point—it's not a real Foo, it's just some new class that quacks like a Foo
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# These have special implementations, but if the user defines
|
45
|
+
# any of them on the object itself, then they'll be replaced with normal
|
46
|
+
# mocked methods. YMMV
|
47
|
+
|
48
|
+
def add_more_methods!(dry_class, type, instance_methods)
|
33
49
|
add_stringify_methods!(dry_class, :to_s, type, instance_methods)
|
34
50
|
add_stringify_methods!(dry_class, :inspect, type, instance_methods)
|
35
51
|
define_method_missing_errors!(dry_class, type, instance_methods)
|
36
52
|
|
37
53
|
define_double_methods!(dry_class, type, instance_methods)
|
38
|
-
|
39
|
-
dry_class
|
40
54
|
end
|
41
55
|
|
42
|
-
private
|
43
|
-
|
44
56
|
def define_double_methods!(dry_class, type, instance_methods)
|
45
|
-
instance_methods.each do |
|
46
|
-
dry_class.undef_method(
|
47
|
-
parameters = type.instance_method(
|
57
|
+
instance_methods.each do |method_name|
|
58
|
+
dry_class.undef_method(method_name) if dry_class.method_defined?(method_name)
|
59
|
+
parameters = @grabs_original_method_parameters.grab(type.instance_method(method_name))
|
48
60
|
signature = @transforms_params.transform(Call.new, params: parameters)
|
49
61
|
method_signature = @stringifies_method_signature.stringify(signature)
|
50
62
|
__mocktail_closure = {
|
51
63
|
dry_class: dry_class,
|
52
64
|
type: type,
|
53
|
-
method:
|
54
|
-
original_method: type.instance_method(
|
65
|
+
method: method_name,
|
66
|
+
original_method: type.instance_method(method_name),
|
55
67
|
signature: signature
|
56
68
|
}
|
57
69
|
|
58
|
-
dry_class.define_method
|
70
|
+
dry_class.define_method method_name,
|
59
71
|
eval(<<-RUBBY, binding, __FILE__, __LINE__ + 1) # standard:disable Security/Eval
|
60
72
|
->#{method_signature} do
|
61
73
|
::Mocktail::Debug.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
|
@@ -3,6 +3,8 @@ require_relative "makes_double/gathers_fakeable_instance_methods"
|
|
3
3
|
|
4
4
|
module Mocktail
|
5
5
|
class MakesDouble
|
6
|
+
extend T::Sig
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
@declares_dry_class = DeclaresDryClass.new
|
8
10
|
@gathers_fakeable_instance_methods = GathersFakeableInstanceMethods.new
|
@@ -11,6 +13,7 @@ module Mocktail
|
|
11
13
|
def make(type)
|
12
14
|
dry_methods = @gathers_fakeable_instance_methods.gather(type)
|
13
15
|
dry_type = @declares_dry_class.declare(type, dry_methods)
|
16
|
+
|
14
17
|
Double.new(
|
15
18
|
original_type: type,
|
16
19
|
dry_type: dry_type,
|
@@ -3,8 +3,10 @@ require_relative "imitates_type/makes_double"
|
|
3
3
|
|
4
4
|
module Mocktail
|
5
5
|
class ImitatesType
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Generic
|
8
|
+
|
6
9
|
def initialize
|
7
|
-
@top_shelf = TopShelf.instance
|
8
10
|
@ensures_imitation_support = EnsuresImitationSupport.new
|
9
11
|
@makes_double = MakesDouble.new
|
10
12
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative "typed"
|
2
|
+
|
3
|
+
# Constant boolean, so won't statically type-check, but `T.unsafe` can't be used
|
4
|
+
# because we haven't required sorbet-runtime yet
|
5
|
+
if eval("Mocktail::TYPED", binding, __FILE__, __LINE__)
|
6
|
+
require "sorbet-runtime"
|
7
|
+
else
|
8
|
+
require "#{Gem.loaded_specs["sorbet-eraser"].gem_dir}/lib/t"
|
9
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class InitializesMocktail
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def init
|
4
6
|
[
|
5
7
|
Mocktail::Matchers::Any,
|
6
8
|
Mocktail::Matchers::Includes,
|
9
|
+
Mocktail::Matchers::IncludesString,
|
10
|
+
Mocktail::Matchers::IncludesKey,
|
11
|
+
Mocktail::Matchers::IncludesHash,
|
7
12
|
Mocktail::Matchers::IsA,
|
8
13
|
Mocktail::Matchers::Matches,
|
9
14
|
Mocktail::Matchers::Not,
|
@@ -1,12 +1,14 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class MatcherPresentation
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def respond_to_missing?(name, include_private = false)
|
4
6
|
!!MatcherRegistry.instance.get(name) || super
|
5
7
|
end
|
6
8
|
|
7
|
-
def method_missing(name, *args, **kwargs, &blk)
|
9
|
+
def method_missing(name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
|
8
10
|
if (matcher = MatcherRegistry.instance.get(name))
|
9
|
-
matcher.new(*args, **kwargs, &blk)
|
11
|
+
matcher.new(*args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
|
10
12
|
else
|
11
13
|
super
|
12
14
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module Mocktail::Matchers
|
2
2
|
class Any < Base
|
3
|
+
extend T::Sig
|
4
|
+
|
3
5
|
def self.matcher_name
|
4
6
|
:any
|
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)
|
@@ -1,17 +1,25 @@
|
|
1
1
|
module Mocktail::Matchers
|
2
2
|
class Base
|
3
|
+
extend T::Sig
|
4
|
+
extend T::Helpers
|
5
|
+
|
6
|
+
if Mocktail::TYPED && T::Private::RuntimeLevels.default_checked_level != :never
|
7
|
+
|
8
|
+
end
|
9
|
+
|
3
10
|
# Custom matchers can receive any args, kwargs, or block they want. Usually
|
4
11
|
# single-argument, though, so that's defaulted here and in #insepct
|
12
|
+
|
5
13
|
def initialize(expected)
|
6
14
|
@expected = expected
|
7
15
|
end
|
8
16
|
|
9
17
|
def self.matcher_name
|
10
|
-
raise Mocktail::
|
18
|
+
raise Mocktail::InvalidMatcherError.new("The `matcher_name` class method must return a valid method name")
|
11
19
|
end
|
12
20
|
|
13
21
|
def match?(actual)
|
14
|
-
raise Mocktail::
|
22
|
+
raise Mocktail::InvalidMatcherError.new("Matchers must implement `match?(argument)`")
|
15
23
|
end
|
16
24
|
|
17
25
|
def inspect
|
@@ -13,7 +13,11 @@ module Mocktail::Matchers
|
|
13
13
|
# See Mockito, which is the earliest implementation I know of:
|
14
14
|
# https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Captor.html
|
15
15
|
class Captor
|
16
|
+
extend T::Sig
|
17
|
+
|
16
18
|
class Capture < Mocktail::Matchers::Base
|
19
|
+
extend T::Sig
|
20
|
+
|
17
21
|
def self.matcher_name
|
18
22
|
:capture
|
19
23
|
end
|
@@ -40,7 +44,12 @@ module Mocktail::Matchers
|
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
47
|
+
# This T.untyped is intentional. Even though a Capture is surely returned,
|
48
|
+
# in order for a verification demonstration to pass its own type check,
|
49
|
+
# it needs to think it's being returned whatever parameter is expected
|
50
|
+
|
43
51
|
attr_reader :capture
|
52
|
+
|
44
53
|
def initialize
|
45
54
|
@capture = Capture.new
|
46
55
|
end
|