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
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class UnsatisfyingCallExplanation
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { returns(UnsatisfyingCall) }
|
8
|
+
attr_reader :reference
|
9
|
+
|
10
|
+
sig { returns(String) }
|
11
|
+
attr_reader :message
|
12
|
+
|
13
|
+
sig { params(reference: UnsatisfyingCall, message: String).void }
|
14
|
+
def initialize(reference, message)
|
15
|
+
@reference = reference
|
16
|
+
@message = message
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T.class_of(UnsatisfyingCallExplanation)) }
|
20
|
+
def type
|
21
|
+
self.class
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "value/cabinet"
|
4
|
+
require_relative "value/call"
|
5
|
+
require_relative "value/demo_config"
|
6
|
+
require_relative "value/explanation"
|
7
|
+
require_relative "value/explanation_data"
|
8
|
+
require_relative "value/double"
|
9
|
+
require_relative "value/double_data"
|
10
|
+
require_relative "value/fake_method_data"
|
11
|
+
require_relative "value/matcher_registry"
|
12
|
+
require_relative "value/no_explanation_data"
|
13
|
+
require_relative "value/signature"
|
14
|
+
require_relative "value/stubbing"
|
15
|
+
require_relative "value/type_replacement"
|
16
|
+
require_relative "value/type_replacement_data"
|
17
|
+
require_relative "value/unsatisfying_call_explanation"
|
18
|
+
require_relative "value/unsatisfying_call"
|
19
|
+
require_relative "value/top_shelf"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "../share/determines_matching_calls"
|
4
|
+
|
5
|
+
module Mocktail
|
6
|
+
class FindsVerifiableCalls
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { void }
|
10
|
+
def initialize
|
11
|
+
@determines_matching_calls = T.let(DeterminesMatchingCalls.new, DeterminesMatchingCalls)
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { params(recording: Call, demo_config: DemoConfig).returns(T::Array[Call]) }
|
15
|
+
def find(recording, demo_config)
|
16
|
+
Mocktail.cabinet.calls.select { |call|
|
17
|
+
@determines_matching_calls.determine(call, recording, demo_config)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
class GathersCallsOfMethod
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(dry_call: Call).returns(T::Array[Call]) }
|
8
|
+
def gather(dry_call)
|
9
|
+
Mocktail.cabinet.calls.select { |call|
|
10
|
+
call.double == dry_call.double &&
|
11
|
+
call.method == dry_call.method
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "raises_verification_error/gathers_calls_of_method"
|
4
|
+
require_relative "../share/stringifies_method_name"
|
5
|
+
require_relative "../share/stringifies_call"
|
6
|
+
|
7
|
+
module Mocktail
|
8
|
+
class RaisesVerificationError
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { void }
|
12
|
+
def initialize
|
13
|
+
@gathers_calls_of_method = T.let(GathersCallsOfMethod.new, GathersCallsOfMethod)
|
14
|
+
@stringifies_method_name = T.let(StringifiesMethodName.new, StringifiesMethodName)
|
15
|
+
@stringifies_call = T.let(StringifiesCall.new, StringifiesCall)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(recording: Call, verifiable_calls: T::Array[Call], demo_config: DemoConfig).void }
|
19
|
+
def raise(recording, verifiable_calls, demo_config)
|
20
|
+
Kernel.raise VerificationError.new <<~MSG
|
21
|
+
Expected mocktail of `#{@stringifies_method_name.stringify(recording)}' to be called like:
|
22
|
+
|
23
|
+
#{@stringifies_call.stringify(recording)}#{[
|
24
|
+
(" [#{demo_config.times} #{pl("time", demo_config.times)}]" unless demo_config.times.nil?),
|
25
|
+
(" [ignoring extra args]" if demo_config.ignore_extra_args),
|
26
|
+
(" [ignoring blocks]" if demo_config.ignore_block)
|
27
|
+
].compact.join(" ")}
|
28
|
+
|
29
|
+
#{[
|
30
|
+
describe_verifiable_times_called(demo_config, verifiable_calls.size),
|
31
|
+
describe_other_calls(recording, verifiable_calls, demo_config)
|
32
|
+
].compact.join("\n\n")}
|
33
|
+
MSG
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
sig { params(demo_config: DemoConfig, count: Integer).returns(T.nilable(String)) }
|
39
|
+
def describe_verifiable_times_called(demo_config, count)
|
40
|
+
return if demo_config.times.nil?
|
41
|
+
|
42
|
+
if count == 0
|
43
|
+
"But it was never called this way."
|
44
|
+
else
|
45
|
+
"But it was actually called this way #{count} #{pl("time", count)}."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(recording: Call, verifiable_calls: T::Array[Call], demo_config: DemoConfig).returns(T.nilable(String)) }
|
50
|
+
def describe_other_calls(recording, verifiable_calls, demo_config)
|
51
|
+
calls_of_method = @gathers_calls_of_method.gather(recording) - verifiable_calls
|
52
|
+
if calls_of_method.size == 0
|
53
|
+
if demo_config.times.nil?
|
54
|
+
"But it was never called."
|
55
|
+
end
|
56
|
+
else
|
57
|
+
<<~MSG
|
58
|
+
It was called differently #{calls_of_method.size} #{pl("time", calls_of_method.size)}:
|
59
|
+
|
60
|
+
#{calls_of_method.map { |call| " " + @stringifies_call.stringify(call) }.join("\n\n")}
|
61
|
+
MSG
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { params(s: String, count: T.nilable(Integer)).returns(String) }
|
66
|
+
def pl(s, count)
|
67
|
+
if count == 1
|
68
|
+
s
|
69
|
+
else
|
70
|
+
s + "s"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require_relative "records_demonstration"
|
4
|
+
require_relative "verifies_call/finds_verifiable_calls"
|
5
|
+
require_relative "verifies_call/raises_verification_error"
|
6
|
+
|
7
|
+
module Mocktail
|
8
|
+
class VerifiesCall
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
sig { void }
|
12
|
+
def initialize
|
13
|
+
@records_demonstration = T.let(RecordsDemonstration.new, RecordsDemonstration)
|
14
|
+
@finds_verifiable_calls = T.let(FindsVerifiableCalls.new, FindsVerifiableCalls)
|
15
|
+
@raises_verification_error = T.let(RaisesVerificationError.new, RaisesVerificationError)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(demo: T.proc.params(matchers: Mocktail::MatcherPresentation).void, demo_config: DemoConfig).void }
|
19
|
+
def verify(demo, demo_config)
|
20
|
+
recording = @records_demonstration.record(demo, demo_config)
|
21
|
+
verifiable_calls = @finds_verifiable_calls.find(recording, demo_config)
|
22
|
+
|
23
|
+
unless verification_satisfied?(verifiable_calls.size, demo_config)
|
24
|
+
@raises_verification_error.raise(recording, verifiable_calls, demo_config)
|
25
|
+
end
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
sig { params(verifiable_call_count: Integer, demo_config: DemoConfig).returns(T::Boolean) }
|
32
|
+
def verification_satisfied?(verifiable_call_count, demo_config)
|
33
|
+
(demo_config.times.nil? && verifiable_call_count > 0) ||
|
34
|
+
(demo_config.times == verifiable_call_count)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Mocktail
|
4
|
+
# The gemspec will define Module::VERSION as loaded from lib/, but if the
|
5
|
+
# user loads mocktail/sorbet, its version file will be effectively redefining
|
6
|
+
# it. Undef it first to ensure we don't spew warnings
|
7
|
+
if defined?(VERSION)
|
8
|
+
Mocktail.send(:remove_const, :VERSION)
|
9
|
+
end
|
10
|
+
|
11
|
+
VERSION = "2.0.0"
|
12
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
if defined?(Mocktail::TYPED)
|
6
|
+
if eval("Mocktail::TYPED", binding, __FILE__, __LINE__)
|
7
|
+
warn "`require \"mocktail\"' was called, but Mocktail was already required as `require \"mocktail/sorbet\"', so we're NOT going to load it to avoid constants from being redefined. If you want to use Mocktail WITHOUT sorbet runtime checks, remove whatever is requiring `mocktail/sorbet'."
|
8
|
+
else
|
9
|
+
warn "`require \"mocktail/sorbet\"' was called, but Mocktail was already required as `require \"mocktail\"', so we're NOT going to load it to avoid constants from being redefined. If you want to use Mocktail WITH sorbet runtime checks, remove whatever is requiring `mocktail'."
|
10
|
+
end
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
require_relative "mocktail/initialize_based_on_type_system_mode_switching"
|
15
|
+
|
16
|
+
require_relative "mocktail/collects_calls"
|
17
|
+
require_relative "mocktail/debug"
|
18
|
+
require_relative "mocktail/dsl"
|
19
|
+
require_relative "mocktail/errors"
|
20
|
+
require_relative "mocktail/explains_thing"
|
21
|
+
require_relative "mocktail/explains_nils"
|
22
|
+
require_relative "mocktail/grabs_original_method_parameters"
|
23
|
+
require_relative "mocktail/handles_dry_call"
|
24
|
+
require_relative "mocktail/handles_dry_new_call"
|
25
|
+
require_relative "mocktail/imitates_type"
|
26
|
+
require_relative "mocktail/initializes_mocktail"
|
27
|
+
require_relative "mocktail/matcher_presentation"
|
28
|
+
require_relative "mocktail/matchers"
|
29
|
+
require_relative "mocktail/raises_neato_no_method_error"
|
30
|
+
require_relative "mocktail/registers_matcher"
|
31
|
+
require_relative "mocktail/registers_stubbing"
|
32
|
+
require_relative "mocktail/replaces_next"
|
33
|
+
require_relative "mocktail/replaces_type"
|
34
|
+
require_relative "mocktail/resets_state"
|
35
|
+
require_relative "mocktail/simulates_argument_error"
|
36
|
+
require_relative "mocktail/stringifies_method_signature"
|
37
|
+
require_relative "mocktail/value"
|
38
|
+
require_relative "mocktail/verifies_call"
|
39
|
+
require_relative "mocktail/version"
|
40
|
+
|
41
|
+
module Mocktail
|
42
|
+
extend T::Sig
|
43
|
+
extend DSL
|
44
|
+
BASE_PATH = T.let((Pathname.new(__FILE__) + "..").to_s, String)
|
45
|
+
|
46
|
+
# Returns an instance of `type` whose implementation is mocked out
|
47
|
+
sig {
|
48
|
+
type_parameters(:T)
|
49
|
+
.params(type: T::Class[T.all(T.type_parameter(:T), Object)])
|
50
|
+
.returns(T.all(T.type_parameter(:T), Object))
|
51
|
+
}
|
52
|
+
def self.of(type)
|
53
|
+
ImitatesType.new.imitate(type)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns an instance of `klass` whose implementation is mocked out AND
|
57
|
+
# stubs its constructor to return that fake the next time klass.new is called
|
58
|
+
sig {
|
59
|
+
type_parameters(:T)
|
60
|
+
.params(type: T::Class[T.all(T.type_parameter(:T), Object)], count: T.nilable(Integer))
|
61
|
+
.returns(T.type_parameter(:T))
|
62
|
+
}
|
63
|
+
def self.of_next(type, count: 1)
|
64
|
+
count ||= 1
|
65
|
+
if count == 1
|
66
|
+
ReplacesNext.new.replace_once(type)
|
67
|
+
elsif !T.unsafe(Mocktail::TYPED) || T::Private::RuntimeLevels.default_checked_level == :never
|
68
|
+
T.unsafe(ReplacesNext.new).replace(type, count)
|
69
|
+
else
|
70
|
+
raise TypeCheckingError.new <<~MSG
|
71
|
+
Calling `Mocktail.of_next()' with a `count' value other than 1 is not supported when
|
72
|
+
type checking is enabled. There are two ways to fix this:
|
73
|
+
|
74
|
+
1. Use `Mocktail.of_next_with_count(type, count)' instead, which will always return
|
75
|
+
an array of fake objects.
|
76
|
+
|
77
|
+
2. Disable runtime type checking by setting `T::Private::RuntimeLevels.default_checked_level = :never'
|
78
|
+
or by setting the envronment variable `SORBET_RUNTIME_DEFAULT_CHECKED_LEVEL=never'
|
79
|
+
MSG
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# An alias of of_next that always returns an array of fakes
|
84
|
+
sig {
|
85
|
+
type_parameters(:T)
|
86
|
+
.params(type: T::Class[T.all(T.type_parameter(:T), Object)], count: Integer)
|
87
|
+
.returns(T::Array[T.type_parameter(:T)])
|
88
|
+
}
|
89
|
+
def self.of_next_with_count(type, count)
|
90
|
+
ReplacesNext.new.replace(type, count)
|
91
|
+
end
|
92
|
+
|
93
|
+
sig { returns(Mocktail::MatcherPresentation) }
|
94
|
+
def self.matchers
|
95
|
+
MatcherPresentation.new
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { returns(Mocktail::Matchers::Captor) }
|
99
|
+
def self.captor
|
100
|
+
Matchers::Captor.new
|
101
|
+
end
|
102
|
+
|
103
|
+
sig { params(matcher: T.class_of(Mocktail::Matchers::Base)).void }
|
104
|
+
def self.register_matcher(matcher)
|
105
|
+
RegistersMatcher.new.register(matcher)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Replaces every singleton method on `type` with a fake, and when instantiated
|
109
|
+
# or included will also fake instance methods
|
110
|
+
sig { params(type: T.any(T::Class[T.anything], Module)).void }
|
111
|
+
def self.replace(type)
|
112
|
+
ReplacesType.new.replace(type)
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
sig { void }
|
117
|
+
def self.reset
|
118
|
+
ResetsState.new.reset
|
119
|
+
end
|
120
|
+
|
121
|
+
sig {
|
122
|
+
params(thing: Object)
|
123
|
+
.returns(Explanation)
|
124
|
+
}
|
125
|
+
def self.explain(thing)
|
126
|
+
ExplainsThing.new.explain(thing)
|
127
|
+
end
|
128
|
+
|
129
|
+
sig { returns(T::Array[Mocktail::UnsatisfyingCallExplanation]) }
|
130
|
+
def self.explain_nils
|
131
|
+
ExplainsNils.new.explain
|
132
|
+
end
|
133
|
+
|
134
|
+
# An alias for Mocktail.explain(double).reference.calls
|
135
|
+
# Takes an optional second parameter of the method name to filter only
|
136
|
+
# calls to that method
|
137
|
+
sig {
|
138
|
+
params(double: Object, method_name: T.nilable(T.any(String, Symbol)))
|
139
|
+
.returns(T::Array[Mocktail::Call])
|
140
|
+
}
|
141
|
+
def self.calls(double, method_name = nil)
|
142
|
+
CollectsCalls.new.collect(double, method_name&.to_sym)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Stores most transactional state about calls & stubbing configurations
|
146
|
+
# Anything returned by this is undocumented and could change at any time, so
|
147
|
+
# don't commit code that relies on it!
|
148
|
+
sig { returns Cabinet }
|
149
|
+
def self.cabinet
|
150
|
+
Thread.current[:mocktail_store] ||= Cabinet.new
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
Mocktail::InitializesMocktail.new.init
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "sorbet/mocktail"
|
@@ -4,8 +4,15 @@ require_relative "../share/bind"
|
|
4
4
|
# is guaranteed by Mocktail to be local to the currently-running thread
|
5
5
|
module Mocktail
|
6
6
|
class Cabinet
|
7
|
+
extend T::Sig
|
8
|
+
|
7
9
|
attr_writer :demonstration_in_progress
|
8
|
-
|
10
|
+
|
11
|
+
attr_reader :calls
|
12
|
+
|
13
|
+
attr_reader :stubbings
|
14
|
+
|
15
|
+
attr_reader :unsatisfying_calls
|
9
16
|
|
10
17
|
def initialize
|
11
18
|
@doubles = []
|
data/lib/mocktail/value/call.rb
CHANGED
@@ -1,14 +1,46 @@
|
|
1
1
|
module Mocktail
|
2
|
-
Call
|
3
|
-
|
4
|
-
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
|
13
|
-
|
2
|
+
class Call < T::Struct
|
3
|
+
extend T::Sig
|
4
|
+
|
5
|
+
const :singleton
|
6
|
+
const :double, default: nil
|
7
|
+
const :original_type
|
8
|
+
const :dry_type
|
9
|
+
const :method, without_accessors: true
|
10
|
+
const :original_method
|
11
|
+
const :args, default: []
|
12
|
+
const :kwargs, default: {}
|
13
|
+
# At present, there's no way to type optional/variadic params in blocks
|
14
|
+
# (i.e. `T.proc.params(*T.untyped).returns(T.untyped)` doesn't work)
|
15
|
+
#
|
16
|
+
# See: https://github.com/sorbet/sorbet/issues/1142#issuecomment-1586195730
|
17
|
+
const :block
|
18
|
+
|
19
|
+
attr_reader :method
|
20
|
+
|
21
|
+
# Because T::Struct compares with referential equality, we need
|
22
|
+
# to redefine the equality methods to compare the values of the attributes.
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
eql?(other)
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
case other
|
30
|
+
when Call
|
31
|
+
[
|
32
|
+
:singleton, :double, :original_type, :dry_type,
|
33
|
+
:method, :original_method, :args, :kwargs, :block
|
34
|
+
].all? { |attr|
|
35
|
+
instance_variable_get("@#{attr}") == other.send(attr)
|
36
|
+
}
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def hash
|
43
|
+
[@singleton, @double, @original_type, @dry_type, @method, @original_method, @args, @kwargs, @block].hash
|
44
|
+
end
|
45
|
+
end
|
14
46
|
end
|
@@ -1,9 +1,8 @@
|
|
1
1
|
module Mocktail
|
2
|
-
DemoConfig
|
3
|
-
:ignore_block,
|
4
|
-
:ignore_extra_args,
|
5
|
-
:ignore_arity,
|
6
|
-
:times,
|
7
|
-
|
8
|
-
)
|
2
|
+
class DemoConfig < T::Struct
|
3
|
+
const :ignore_block, default: false
|
4
|
+
const :ignore_extra_args, default: false
|
5
|
+
const :ignore_arity, default: false
|
6
|
+
const :times, default: nil
|
7
|
+
end
|
9
8
|
end
|
@@ -1,9 +1,13 @@
|
|
1
|
+
require_relative "call"
|
2
|
+
require_relative "stubbing"
|
3
|
+
|
1
4
|
module Mocktail
|
2
|
-
DoubleData
|
3
|
-
|
4
|
-
|
5
|
-
:
|
6
|
-
:
|
7
|
-
|
8
|
-
|
5
|
+
class DoubleData < T::Struct
|
6
|
+
include ExplanationData
|
7
|
+
|
8
|
+
const :type
|
9
|
+
const :double
|
10
|
+
const :calls
|
11
|
+
const :stubbings
|
12
|
+
end
|
9
13
|
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module Mocktail
|
2
2
|
class Explanation
|
3
|
-
|
3
|
+
extend T::Sig
|
4
|
+
|
5
|
+
attr_reader :reference
|
6
|
+
|
7
|
+
attr_reader :message
|
4
8
|
|
5
9
|
def initialize(reference, message)
|
6
10
|
@reference = reference
|
@@ -13,17 +17,38 @@ module Mocktail
|
|
13
17
|
end
|
14
18
|
|
15
19
|
class NoExplanation < Explanation
|
16
|
-
|
20
|
+
attr_reader :reference
|
17
21
|
|
18
|
-
|
22
|
+
def initialize(reference, message)
|
23
|
+
@reference = reference
|
24
|
+
@message = message
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
28
|
class DoubleExplanation < Explanation
|
29
|
+
attr_reader :reference
|
30
|
+
|
31
|
+
def initialize(reference, message)
|
32
|
+
@reference = reference
|
33
|
+
@message = message
|
34
|
+
end
|
22
35
|
end
|
23
36
|
|
24
37
|
class ReplacedTypeExplanation < Explanation
|
38
|
+
attr_reader :reference
|
39
|
+
|
40
|
+
def initialize(reference, message)
|
41
|
+
@reference = reference
|
42
|
+
@message = message
|
43
|
+
end
|
25
44
|
end
|
26
45
|
|
27
46
|
class FakeMethodExplanation < Explanation
|
47
|
+
attr_reader :reference
|
48
|
+
|
49
|
+
def initialize(reference, message)
|
50
|
+
@reference = reference
|
51
|
+
@message = message
|
52
|
+
end
|
28
53
|
end
|
29
54
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mocktail
|
2
|
+
class NoExplanationData < T::Struct
|
3
|
+
extend T::Sig
|
4
|
+
include ExplanationData
|
5
|
+
|
6
|
+
const :thing
|
7
|
+
|
8
|
+
def calls
|
9
|
+
raise Error.new("No calls have been recorded for #{thing.inspect}, because Mocktail doesn't know what it is.")
|
10
|
+
end
|
11
|
+
|
12
|
+
def stubbings
|
13
|
+
raise Error.new("No stubbings exist on #{thing.inspect}, because Mocktail doesn't know what it is.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,32 +1,11 @@
|
|
1
1
|
module Mocktail
|
2
|
-
|
3
|
-
|
4
|
-
:positional_args,
|
5
|
-
:keyword_params,
|
6
|
-
:keyword_args,
|
7
|
-
:block_param,
|
8
|
-
:block_arg,
|
9
|
-
keyword_init: true
|
10
|
-
)
|
11
|
-
class Signature
|
12
|
-
DEFAULT_REST_ARGS = "args"
|
13
|
-
DEFAULT_REST_KWARGS = "kwargs"
|
14
|
-
DEFAULT_BLOCK_PARAM = "blk"
|
15
|
-
end
|
2
|
+
class Params < T::Struct
|
3
|
+
extend T::Sig
|
16
4
|
|
17
|
-
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:rest,
|
22
|
-
keyword_init: true
|
23
|
-
) do
|
24
|
-
def initialize(**params)
|
25
|
-
super
|
26
|
-
self.all ||= []
|
27
|
-
self.required ||= []
|
28
|
-
self.optional ||= []
|
29
|
-
end
|
5
|
+
prop :all, default: []
|
6
|
+
prop :required, default: []
|
7
|
+
prop :optional, default: []
|
8
|
+
prop :rest
|
30
9
|
|
31
10
|
def allowed
|
32
11
|
all.select { |name| required.include?(name) || optional.include?(name) }
|
@@ -36,4 +15,17 @@ module Mocktail
|
|
36
15
|
!!rest
|
37
16
|
end
|
38
17
|
end
|
18
|
+
|
19
|
+
class Signature < T::Struct
|
20
|
+
const :positional_params
|
21
|
+
const :positional_args
|
22
|
+
const :keyword_params
|
23
|
+
const :keyword_args
|
24
|
+
const :block_param
|
25
|
+
const :block_arg, default: nil
|
26
|
+
|
27
|
+
DEFAULT_REST_ARGS = "args"
|
28
|
+
DEFAULT_REST_KWARGS = "kwargs"
|
29
|
+
DEFAULT_BLOCK_PARAM = "blk"
|
30
|
+
end
|
39
31
|
end
|