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
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
|