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
@@ -0,0 +1,58 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "../share/bind"
|
4
|
+
|
5
|
+
module Mocktail
|
6
|
+
class TransformsParams
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { void }
|
10
|
+
def initialize
|
11
|
+
@grabs_original_method_parameters = T.let(GrabsOriginalMethodParameters.new, GrabsOriginalMethodParameters)
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { params(dry_call: Call, params: T.nilable(T::Array[T::Array[Symbol]])).returns(Signature) }
|
15
|
+
def transform(dry_call, params: nil)
|
16
|
+
params ||= @grabs_original_method_parameters.grab(dry_call.original_method)
|
17
|
+
params = name_unnamed_params(params)
|
18
|
+
|
19
|
+
Signature.new(
|
20
|
+
positional_params: Params.new(
|
21
|
+
all: params.select { |t, _|
|
22
|
+
[:req, :opt, :rest].any? { |param_type| Bind.call(t, :==, param_type) }
|
23
|
+
}.map { |pair| pair.fetch(1) },
|
24
|
+
required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |pair| pair.fetch(1) },
|
25
|
+
optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |pair| pair.fetch(1) },
|
26
|
+
rest: params.find { |t, _| Bind.call(t, :==, :rest) }&.last
|
27
|
+
),
|
28
|
+
positional_args: dry_call.args,
|
29
|
+
|
30
|
+
keyword_params: Params.new(
|
31
|
+
all: params.select { |type, _|
|
32
|
+
[:keyreq, :key, :keyrest].include?(type)
|
33
|
+
}.map { |pair| pair.fetch(1) },
|
34
|
+
required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |pair| pair.fetch(1) },
|
35
|
+
optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |pair| pair.fetch(1) },
|
36
|
+
rest: params.find { |t, _| Bind.call(t, :==, :keyrest) }&.last
|
37
|
+
),
|
38
|
+
keyword_args: dry_call.kwargs,
|
39
|
+
|
40
|
+
block_param: params.find { |t, _| Bind.call(t, :==, :block) }&.last,
|
41
|
+
block_arg: dry_call.block
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
sig { params(params: T::Array[T::Array[Symbol]]).returns(T::Array[T::Array[Symbol]]) }
|
48
|
+
def name_unnamed_params(params)
|
49
|
+
params.map.with_index { |param, i|
|
50
|
+
if param.size == 1
|
51
|
+
param + ["unnamed_arg_#{i + 1}".to_sym]
|
52
|
+
else
|
53
|
+
param
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "simulates_argument_error/transforms_params"
|
4
|
+
require_relative "simulates_argument_error/reconciles_args_with_params"
|
5
|
+
require_relative "simulates_argument_error/recreates_message"
|
6
|
+
require_relative "share/cleans_backtrace"
|
7
|
+
require_relative "share/stringifies_call"
|
8
|
+
|
9
|
+
module Mocktail
|
10
|
+
class SimulatesArgumentError
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { void }
|
14
|
+
def initialize
|
15
|
+
@transforms_params = T.let(TransformsParams.new, TransformsParams)
|
16
|
+
@reconciles_args_with_params = T.let(ReconcilesArgsWithParams.new, ReconcilesArgsWithParams)
|
17
|
+
@recreates_message = T.let(RecreatesMessage.new, RecreatesMessage)
|
18
|
+
@cleans_backtrace = T.let(CleansBacktrace.new, CleansBacktrace)
|
19
|
+
@stringifies_call = T.let(StringifiesCall.new, StringifiesCall)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(dry_call: Call).returns(T.nilable(ArgumentError)) }
|
23
|
+
def simulate(dry_call)
|
24
|
+
signature = @transforms_params.transform(dry_call)
|
25
|
+
|
26
|
+
unless @reconciles_args_with_params.reconcile(signature)
|
27
|
+
@cleans_backtrace.clean(
|
28
|
+
ArgumentError.new([
|
29
|
+
@recreates_message.recreate(signature),
|
30
|
+
"[Mocktail call: `#{@stringifies_call.stringify(dry_call)}']"
|
31
|
+
].join(" "))
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class StringifiesMethodSignature
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(signature: Signature).returns(String) }
|
8
|
+
def stringify(signature)
|
9
|
+
positional_params = positional(signature)
|
10
|
+
keyword_params = keyword(signature)
|
11
|
+
block_param = block(signature)
|
12
|
+
|
13
|
+
"(#{[positional_params, keyword_params, block_param].compact.join(", ")})"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
sig { params(signature: Signature).returns(T.nilable(String)) }
|
19
|
+
def positional(signature)
|
20
|
+
params = signature.positional_params.all.map do |name|
|
21
|
+
if signature.positional_params.allowed.include?(name)
|
22
|
+
"#{name} = ((__mocktail_default_args ||= {})[:#{name}] = nil)"
|
23
|
+
elsif signature.positional_params.rest == name
|
24
|
+
"*#{(name == :*) ? Signature::DEFAULT_REST_ARGS : name}"
|
25
|
+
end
|
26
|
+
end.compact
|
27
|
+
|
28
|
+
params.join(", ") if params.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(signature: Signature).returns(T.nilable(String)) }
|
32
|
+
def keyword(signature)
|
33
|
+
params = signature.keyword_params.all.map do |name|
|
34
|
+
if signature.keyword_params.allowed.include?(name)
|
35
|
+
"#{name}: ((__mocktail_default_args ||= {})[:#{name}] = nil)"
|
36
|
+
elsif signature.keyword_params.rest == name
|
37
|
+
"**#{(name == :**) ? Signature::DEFAULT_REST_KWARGS : name}"
|
38
|
+
end
|
39
|
+
end.compact
|
40
|
+
|
41
|
+
params.join(", ") if params.any?
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(signature: Signature).returns(String) }
|
45
|
+
def block(signature)
|
46
|
+
if signature.block_param && signature.block_param != :&
|
47
|
+
"&#{signature.block_param}"
|
48
|
+
else
|
49
|
+
"&#{Signature::DEFAULT_BLOCK_PARAM}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "../share/bind"
|
4
|
+
|
5
|
+
# The Cabinet stores all thread-local state, so anything that goes here
|
6
|
+
# is guaranteed by Mocktail to be local to the currently-running thread
|
7
|
+
module Mocktail
|
8
|
+
class Cabinet
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { params(demonstration_in_progress: T::Boolean).void }
|
12
|
+
attr_writer :demonstration_in_progress
|
13
|
+
|
14
|
+
sig { returns(T::Array[Call]) }
|
15
|
+
attr_reader :calls
|
16
|
+
|
17
|
+
sig { returns(T::Array[Stubbing[T.anything]]) }
|
18
|
+
attr_reader :stubbings
|
19
|
+
|
20
|
+
sig { returns(T::Array[UnsatisfyingCall]) }
|
21
|
+
attr_reader :unsatisfying_calls
|
22
|
+
|
23
|
+
sig { void }
|
24
|
+
def initialize
|
25
|
+
@doubles = T.let([], T::Array[Double])
|
26
|
+
@calls = T.let([], T::Array[Call])
|
27
|
+
@stubbings = T.let([], T::Array[Stubbing[T.anything]])
|
28
|
+
@unsatisfying_calls = T.let([], T::Array[UnsatisfyingCall])
|
29
|
+
@demonstration_in_progress = T.let(false, T::Boolean)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { void }
|
33
|
+
def reset!
|
34
|
+
@calls = []
|
35
|
+
@stubbings = []
|
36
|
+
@unsatisfying_calls = []
|
37
|
+
# Could cause an exception or prevent pollution—you decide!
|
38
|
+
@demonstration_in_progress = false
|
39
|
+
# note we don't reset doubles as they don't carry any
|
40
|
+
# user-meaningful state on them, and clearing them on reset could result
|
41
|
+
# in valid mocks being broken and stop working
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(double: Double).void }
|
45
|
+
def store_double(double)
|
46
|
+
@doubles << double
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(call: Call).void }
|
50
|
+
def store_call(call)
|
51
|
+
@calls << call
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(stubbing: Stubbing[T.anything]).void }
|
55
|
+
def store_stubbing(stubbing)
|
56
|
+
@stubbings << stubbing
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(unsatisfying_call: UnsatisfyingCall).void }
|
60
|
+
def store_unsatisfying_call(unsatisfying_call)
|
61
|
+
@unsatisfying_calls << unsatisfying_call
|
62
|
+
end
|
63
|
+
|
64
|
+
sig { returns(T::Boolean) }
|
65
|
+
def demonstration_in_progress?
|
66
|
+
@demonstration_in_progress
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(thing: T.anything).returns(T.nilable(Double)) }
|
70
|
+
def double_for_instance(thing)
|
71
|
+
@doubles.find { |double|
|
72
|
+
# Intentionally calling directly to avoid an infinite recursion in Bind.call
|
73
|
+
Object.instance_method(:==).bind_call(double.dry_instance, thing)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { params(double: Double).returns(T::Array[Stubbing[T.anything]]) }
|
78
|
+
def stubbings_for_double(double)
|
79
|
+
@stubbings.select { |stubbing|
|
80
|
+
Bind.call(stubbing.recording.double, :==, double.dry_instance)
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(double: Double).returns(T::Array[Call]) }
|
85
|
+
def calls_for_double(double)
|
86
|
+
@calls.select { |call|
|
87
|
+
Bind.call(call.double, :==, double.dry_instance)
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class Call < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
const :singleton, T.nilable(T::Boolean)
|
8
|
+
const :double, Object, default: nil
|
9
|
+
const :original_type, T.nilable(T.any(T::Class[T.anything], Module))
|
10
|
+
const :dry_type, T.nilable(T.any(T::Class[T.anything], Module))
|
11
|
+
const :method, T.nilable(Symbol), without_accessors: true
|
12
|
+
const :original_method, T.nilable(T.any(UnboundMethod, Method))
|
13
|
+
const :args, T::Array[T.untyped], default: []
|
14
|
+
const :kwargs, T::Hash[Symbol, T.untyped], default: {}
|
15
|
+
# At present, there's no way to type optional/variadic params in blocks
|
16
|
+
# (i.e. `T.proc.params(*T.untyped).returns(T.untyped)` doesn't work)
|
17
|
+
#
|
18
|
+
# See: https://github.com/sorbet/sorbet/issues/1142#issuecomment-1586195730
|
19
|
+
const :block, T.nilable(Proc)
|
20
|
+
|
21
|
+
sig { returns(T.nilable(Symbol)) }
|
22
|
+
attr_reader :method
|
23
|
+
|
24
|
+
# Because T::Struct compares with referential equality, we need
|
25
|
+
# to redefine the equality methods to compare the values of the attributes.
|
26
|
+
sig { params(other: T.nilable(T.anything)).returns(T::Boolean) }
|
27
|
+
def ==(other)
|
28
|
+
eql?(other)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(other: T.anything).returns(T::Boolean) }
|
32
|
+
def eql?(other)
|
33
|
+
case other
|
34
|
+
when Call
|
35
|
+
[
|
36
|
+
:singleton, :double, :original_type, :dry_type,
|
37
|
+
:method, :original_method, :args, :kwargs, :block
|
38
|
+
].all? { |attr|
|
39
|
+
instance_variable_get("@#{attr}") == other.send(attr)
|
40
|
+
}
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { returns(Integer) }
|
47
|
+
def hash
|
48
|
+
[@singleton, @double, @original_type, @dry_type, @method, @original_method, @args, @kwargs, @block].hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class DemoConfig < T::Struct
|
5
|
+
const :ignore_block, T::Boolean, default: false
|
6
|
+
const :ignore_extra_args, T::Boolean, default: false
|
7
|
+
const :ignore_arity, T::Boolean, default: false
|
8
|
+
const :times, T.nilable(Integer), default: nil
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "call"
|
4
|
+
require_relative "stubbing"
|
5
|
+
|
6
|
+
module Mocktail
|
7
|
+
class DoubleData < T::Struct
|
8
|
+
include ExplanationData
|
9
|
+
|
10
|
+
const :type, T.any(T::Class[T.anything], Module)
|
11
|
+
const :double, Object
|
12
|
+
const :calls, T::Array[Call]
|
13
|
+
const :stubbings, T::Array[Stubbing[T.anything]]
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class Explanation
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { returns(Mocktail::ExplanationData) }
|
8
|
+
attr_reader :reference
|
9
|
+
|
10
|
+
sig { returns(String) }
|
11
|
+
attr_reader :message
|
12
|
+
|
13
|
+
sig { params(reference: Mocktail::ExplanationData, message: String).void }
|
14
|
+
def initialize(reference, message)
|
15
|
+
@reference = reference
|
16
|
+
@message = message
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T.class_of(Explanation)) }
|
20
|
+
def type
|
21
|
+
self.class
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NoExplanation < Explanation
|
26
|
+
sig { override.returns(NoExplanationData) }
|
27
|
+
attr_reader :reference
|
28
|
+
|
29
|
+
sig { params(reference: NoExplanationData, message: String).void }
|
30
|
+
def initialize(reference, message)
|
31
|
+
@reference = reference
|
32
|
+
@message = message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class DoubleExplanation < Explanation
|
37
|
+
sig { override.returns(DoubleData) }
|
38
|
+
attr_reader :reference
|
39
|
+
|
40
|
+
sig { params(reference: DoubleData, message: String).void }
|
41
|
+
def initialize(reference, message)
|
42
|
+
@reference = reference
|
43
|
+
@message = message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ReplacedTypeExplanation < Explanation
|
48
|
+
sig { override.returns(TypeReplacementData) }
|
49
|
+
attr_reader :reference
|
50
|
+
|
51
|
+
sig { params(reference: TypeReplacementData, message: String).void }
|
52
|
+
def initialize(reference, message)
|
53
|
+
@reference = reference
|
54
|
+
@message = message
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class FakeMethodExplanation < Explanation
|
59
|
+
sig { override.returns(FakeMethodData) }
|
60
|
+
attr_reader :reference
|
61
|
+
|
62
|
+
sig { params(reference: FakeMethodData, message: String).void }
|
63
|
+
def initialize(reference, message)
|
64
|
+
@reference = reference
|
65
|
+
@message = message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
module ExplanationData
|
5
|
+
extend T::Helpers
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
interface!
|
9
|
+
include Kernel
|
10
|
+
|
11
|
+
sig { abstract.returns T::Array[Mocktail::Call] }
|
12
|
+
def calls
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { abstract.returns T::Array[Mocktail::Stubbing[T.anything]] }
|
16
|
+
def stubbings
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class MatcherRegistry
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { returns(MatcherRegistry) }
|
8
|
+
def self.instance
|
9
|
+
@matcher_registry ||= T.let(new, T.nilable(T.attached_class))
|
10
|
+
end
|
11
|
+
|
12
|
+
sig { void }
|
13
|
+
def initialize
|
14
|
+
@matchers = T.let({}, T::Hash[Symbol, T.class_of(Matchers::Base)])
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(matcher_type: T.class_of(Matchers::Base)).void }
|
18
|
+
def add(matcher_type)
|
19
|
+
@matchers[matcher_type.matcher_name] = matcher_type
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(name: Symbol).returns(T.nilable(T.class_of(Matchers::Base))) }
|
23
|
+
def get(name)
|
24
|
+
@matchers[name]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class NoExplanationData < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
include ExplanationData
|
7
|
+
|
8
|
+
const :thing, Object
|
9
|
+
|
10
|
+
sig { override.returns(T::Array[Mocktail::Call]) }
|
11
|
+
def calls
|
12
|
+
raise Error.new("No calls have been recorded for #{thing.inspect}, because Mocktail doesn't know what it is.")
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.returns T::Array[Mocktail::Stubbing[T.anything]] }
|
16
|
+
def stubbings
|
17
|
+
raise Error.new("No stubbings exist on #{thing.inspect}, because Mocktail doesn't know what it is.")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class Params < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
prop :all, T::Array[Symbol], default: []
|
8
|
+
prop :required, T::Array[Symbol], default: []
|
9
|
+
prop :optional, T::Array[Symbol], default: []
|
10
|
+
prop :rest, T.nilable(Symbol)
|
11
|
+
|
12
|
+
sig { returns(T::Array[Symbol]) }
|
13
|
+
def allowed
|
14
|
+
all.select { |name| required.include?(name) || optional.include?(name) }
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { returns(T::Boolean) }
|
18
|
+
def rest?
|
19
|
+
!!rest
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Signature < T::Struct
|
24
|
+
const :positional_params, Params
|
25
|
+
const :positional_args, T::Array[T.anything]
|
26
|
+
const :keyword_params, Params
|
27
|
+
const :keyword_args, T::Hash[Symbol, T.anything]
|
28
|
+
const :block_param, T.nilable(Symbol)
|
29
|
+
const :block_arg, T.nilable(Proc), default: nil
|
30
|
+
|
31
|
+
DEFAULT_REST_ARGS = "args"
|
32
|
+
DEFAULT_REST_KWARGS = "kwargs"
|
33
|
+
DEFAULT_BLOCK_PARAM = "blk"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class Stubbing < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
extend T::Generic
|
7
|
+
MethodReturnType = type_member
|
8
|
+
|
9
|
+
const :demonstration, T.proc.params(matchers: Mocktail::MatcherPresentation).returns(MethodReturnType)
|
10
|
+
const :demo_config, DemoConfig
|
11
|
+
prop :satisfaction_count, Integer, default: 0
|
12
|
+
const :recording, Call
|
13
|
+
prop :effect, T.nilable(T.proc.params(call: Mocktail::Call).returns(MethodReturnType))
|
14
|
+
|
15
|
+
sig { void }
|
16
|
+
def satisfied!
|
17
|
+
self.satisfaction_count += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { params(block: T.proc.params(call: Mocktail::Call).returns(MethodReturnType)).void }
|
21
|
+
def with(&block)
|
22
|
+
self.effect = block
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
# The TopShelf is where we keep all the more global, dangerous state.
|
5
|
+
# In particular, this is where Mocktail manages state related to singleton
|
6
|
+
# method replacements carried out with Mocktail.replace(ClassOrModule)
|
7
|
+
class TopShelf
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(TopShelf) }
|
11
|
+
def self.instance
|
12
|
+
Thread.current[:mocktail_top_shelf] ||= new
|
13
|
+
end
|
14
|
+
|
15
|
+
@@type_replacements = T.let({}, T::Hash[T.any(Module, T::Class[T.anything]), TypeReplacement])
|
16
|
+
@@type_replacements_mutex = T.let(Mutex.new, Mutex)
|
17
|
+
|
18
|
+
sig { void }
|
19
|
+
def initialize
|
20
|
+
@new_registrations = T.let([], T::Array[T.any(Module, T::Class[T.anything])])
|
21
|
+
@of_next_registrations = T.let([], T::Array[T::Class[T.anything]])
|
22
|
+
@singleton_method_registrations = T.let([], T::Array[T.any(Module, T::Class[T.anything])])
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(type: T.any(Module, T::Class[T.anything])).returns(TypeReplacement) }
|
26
|
+
def type_replacement_for(type)
|
27
|
+
@@type_replacements_mutex.synchronize {
|
28
|
+
@@type_replacements[type] ||= TypeReplacement.new(type: type)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(type: T.any(Module, T::Class[T.anything])).returns(T.nilable(TypeReplacement)) }
|
33
|
+
def type_replacement_if_exists_for(type)
|
34
|
+
@@type_replacements_mutex.synchronize {
|
35
|
+
@@type_replacements[type]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { void }
|
40
|
+
def reset_current_thread!
|
41
|
+
Thread.current[:mocktail_top_shelf] = self.class.new
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(type: T.any(Module, T::Class[T.anything])).void }
|
45
|
+
def register_new_replacement!(type)
|
46
|
+
@new_registrations |= [type]
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(type: T.any(Module, T::Class[T.anything])).returns(T::Boolean) }
|
50
|
+
def new_replaced?(type)
|
51
|
+
@new_registrations.include?(type)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(type: T::Class[T.anything]).void }
|
55
|
+
def register_of_next_replacement!(type)
|
56
|
+
@of_next_registrations |= [type]
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(type: T::Class[T.anything]).returns(T::Boolean) }
|
60
|
+
def of_next_registered?(type)
|
61
|
+
@of_next_registrations.include?(type)
|
62
|
+
end
|
63
|
+
|
64
|
+
sig { params(type: T::Class[T.anything]).void }
|
65
|
+
def unregister_of_next_replacement!(type)
|
66
|
+
@of_next_registrations -= [type]
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(type: T.any(Module, T::Class[T.anything])).void }
|
70
|
+
def register_singleton_method_replacement!(type)
|
71
|
+
@singleton_method_registrations |= [type]
|
72
|
+
end
|
73
|
+
|
74
|
+
sig { params(type: T.any(Module, T::Class[T.anything])).returns(T::Boolean) }
|
75
|
+
def singleton_methods_replaced?(type)
|
76
|
+
@singleton_method_registrations.include?(type)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class TypeReplacement < T::Struct
|
5
|
+
const :type, T.any(T::Class[T.anything], Module)
|
6
|
+
prop :original_methods, T.nilable(T::Array[Method])
|
7
|
+
prop :replacement_methods, T.nilable(T::Array[Method])
|
8
|
+
prop :original_new, T.nilable(Method)
|
9
|
+
prop :replacement_new, T.nilable(Method)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class TypeReplacementData < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
const :type, T.any(T::Class[T.anything], Module)
|
8
|
+
const :replaced_method_names, T::Array[Symbol]
|
9
|
+
const :calls, T::Array[Call]
|
10
|
+
const :stubbings, T::Array[Stubbing[T.anything]]
|
11
|
+
|
12
|
+
include ExplanationData
|
13
|
+
|
14
|
+
sig { returns(T.any(T::Class[T.anything], Module)) }
|
15
|
+
def double
|
16
|
+
type
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|