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.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +6 -5
  3. data/.gitignore +3 -0
  4. data/.standard.yml +8 -0
  5. data/CHANGELOG.md +14 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +98 -25
  8. data/README.md +18 -922
  9. data/Rakefile +0 -1
  10. data/bin/console +1 -2
  11. data/bin/tapioca +29 -0
  12. data/lib/mocktail/collects_calls.rb +2 -0
  13. data/lib/mocktail/debug.rb +13 -10
  14. data/lib/mocktail/dsl.rb +2 -0
  15. data/lib/mocktail/errors.rb +2 -0
  16. data/lib/mocktail/explains_nils.rb +2 -0
  17. data/lib/mocktail/explains_thing.rb +7 -4
  18. data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
  19. data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
  20. data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
  21. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
  22. data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
  23. data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
  24. data/lib/mocktail/handles_dry_call.rb +2 -0
  25. data/lib/mocktail/handles_dry_new_call.rb +2 -0
  26. data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
  27. data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
  28. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
  29. data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
  30. data/lib/mocktail/imitates_type/makes_double.rb +3 -0
  31. data/lib/mocktail/imitates_type.rb +3 -1
  32. data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
  33. data/lib/mocktail/initializes_mocktail.rb +5 -0
  34. data/lib/mocktail/matcher_presentation.rb +4 -2
  35. data/lib/mocktail/matchers/any.rb +4 -3
  36. data/lib/mocktail/matchers/base.rb +10 -2
  37. data/lib/mocktail/matchers/captor.rb +9 -0
  38. data/lib/mocktail/matchers/includes.rb +2 -0
  39. data/lib/mocktail/matchers/includes_hash.rb +9 -0
  40. data/lib/mocktail/matchers/includes_key.rb +9 -0
  41. data/lib/mocktail/matchers/includes_string.rb +9 -0
  42. data/lib/mocktail/matchers/is_a.rb +2 -0
  43. data/lib/mocktail/matchers/matches.rb +2 -0
  44. data/lib/mocktail/matchers/not.rb +2 -0
  45. data/lib/mocktail/matchers/numeric.rb +5 -4
  46. data/lib/mocktail/matchers/that.rb +2 -0
  47. data/lib/mocktail/matchers.rb +3 -0
  48. data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
  49. data/lib/mocktail/records_demonstration.rb +2 -0
  50. data/lib/mocktail/registers_matcher.rb +8 -3
  51. data/lib/mocktail/registers_stubbing.rb +2 -0
  52. data/lib/mocktail/replaces_next.rb +7 -1
  53. data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
  54. data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
  55. data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
  56. data/lib/mocktail/replaces_type.rb +6 -0
  57. data/lib/mocktail/resets_state.rb +2 -0
  58. data/lib/mocktail/share/bind.rb +7 -5
  59. data/lib/mocktail/share/cleans_backtrace.rb +3 -5
  60. data/lib/mocktail/share/creates_identifier.rb +16 -9
  61. data/lib/mocktail/share/determines_matching_calls.rb +4 -2
  62. data/lib/mocktail/share/stringifies_call.rb +6 -2
  63. data/lib/mocktail/share/stringifies_method_name.rb +3 -1
  64. data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
  65. data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
  66. data/lib/mocktail/simulates_argument_error/transforms_params.rb +15 -8
  67. data/lib/mocktail/simulates_argument_error.rb +2 -0
  68. data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
  69. data/lib/mocktail/sorbet/mocktail/debug.rb +54 -0
  70. data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
  71. data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
  72. data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
  73. data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
  74. data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
  75. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  76. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  77. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  78. data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
  79. data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  80. data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
  81. data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
  82. data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  83. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  84. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  85. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  86. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
  87. data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
  88. data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  89. data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
  90. data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
  91. data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
  92. data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
  93. data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
  94. data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
  95. data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
  96. data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
  97. data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
  98. data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
  99. data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
  100. data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
  101. data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
  102. data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
  103. data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
  104. data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
  105. data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
  106. data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
  107. data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
  108. data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
  109. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
  110. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  111. data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  112. data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
  113. data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
  114. data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
  115. data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
  116. data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
  117. data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
  118. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +85 -0
  119. data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
  120. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  121. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  122. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  123. data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
  124. data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
  125. data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
  126. data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
  127. data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
  128. data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
  129. data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
  130. data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
  131. data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
  132. data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
  133. data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
  134. data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
  135. data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
  136. data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
  137. data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
  138. data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
  139. data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
  140. data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
  141. data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
  142. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
  143. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  144. data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
  145. data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  146. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  147. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
  148. data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
  149. data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
  150. data/lib/mocktail/sorbet/mocktail.rb +154 -0
  151. data/lib/mocktail/sorbet.rb +1 -0
  152. data/lib/mocktail/stringifies_method_signature.rb +2 -0
  153. data/lib/mocktail/typed.rb +3 -0
  154. data/lib/mocktail/value/cabinet.rb +8 -1
  155. data/lib/mocktail/value/call.rb +44 -12
  156. data/lib/mocktail/value/demo_config.rb +6 -7
  157. data/lib/mocktail/value/double.rb +6 -7
  158. data/lib/mocktail/value/double_data.rb +11 -7
  159. data/lib/mocktail/value/explanation.rb +28 -3
  160. data/lib/mocktail/value/explanation_data.rb +14 -0
  161. data/lib/mocktail/value/fake_method_data.rb +7 -6
  162. data/lib/mocktail/value/matcher_registry.rb +2 -0
  163. data/lib/mocktail/value/no_explanation_data.rb +16 -0
  164. data/lib/mocktail/value/signature.rb +19 -27
  165. data/lib/mocktail/value/stubbing.rb +11 -12
  166. data/lib/mocktail/value/top_shelf.rb +5 -0
  167. data/lib/mocktail/value/type_replacement.rb +7 -8
  168. data/lib/mocktail/value/type_replacement_data.rb +10 -7
  169. data/lib/mocktail/value/unsatisfying_call.rb +5 -6
  170. data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
  171. data/lib/mocktail/value.rb +5 -2
  172. data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
  173. data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
  174. data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
  175. data/lib/mocktail/verifies_call.rb +3 -0
  176. data/lib/mocktail/version.rb +8 -1
  177. data/lib/mocktail.rb +46 -5
  178. data/mocktail.gemspec +8 -4
  179. data/rbi/mocktail-pregenerated.rbi +1865 -0
  180. data/rbi/mocktail.rbi +77 -0
  181. data/rbi/sorbet-runtime.rbi +29 -0
  182. data/spoom_report.html +1248 -0
  183. 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
@@ -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"
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class StringifiesMethodSignature
3
+ extend T::Sig
4
+
3
5
  def stringify(signature)
4
6
  positional_params = positional(signature)
5
7
  keyword_params = keyword(signature)
@@ -0,0 +1,3 @@
1
+ module Mocktail
2
+ TYPED = false
3
+ end
@@ -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
- attr_reader :calls, :stubbings, :unsatisfying_calls
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 = []
@@ -1,14 +1,46 @@
1
1
  module Mocktail
2
- Call = Struct.new(
3
- :singleton,
4
- :double,
5
- :original_type,
6
- :dry_type,
7
- :method,
8
- :original_method,
9
- :args,
10
- :kwargs,
11
- :block,
12
- keyword_init: true
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 = Struct.new(
3
- :ignore_block,
4
- :ignore_extra_args,
5
- :ignore_arity,
6
- :times,
7
- keyword_init: true
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,8 @@
1
1
  module Mocktail
2
- Double = Struct.new(
3
- :original_type,
4
- :dry_type,
5
- :dry_instance,
6
- :dry_methods,
7
- keyword_init: true
8
- )
2
+ class Double < T::Struct
3
+ const :original_type
4
+ const :dry_type
5
+ const :dry_instance
6
+ const :dry_methods
7
+ end
9
8
  end
@@ -1,9 +1,13 @@
1
+ require_relative "call"
2
+ require_relative "stubbing"
3
+
1
4
  module Mocktail
2
- DoubleData = Struct.new(
3
- :type,
4
- :double,
5
- :calls,
6
- :stubbings,
7
- keyword_init: true
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
- attr_reader :reference, :message
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
- end
20
+ attr_reader :reference
17
21
 
18
- class UnsatisfyingCallExplanation < Explanation
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,14 @@
1
+ module Mocktail
2
+ module ExplanationData
3
+ extend T::Helpers
4
+ extend T::Sig
5
+
6
+ include Kernel
7
+
8
+ def calls
9
+ end
10
+
11
+ def stubbings
12
+ end
13
+ end
14
+ end
@@ -1,8 +1,9 @@
1
1
  module Mocktail
2
- FakeMethodData = Struct.new(
3
- :receiver,
4
- :calls,
5
- :stubbings,
6
- keyword_init: true
7
- )
2
+ class FakeMethodData < T::Struct
3
+ include ExplanationData
4
+
5
+ const :receiver
6
+ const :calls
7
+ const :stubbings
8
+ end
8
9
  end
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class MatcherRegistry
3
+ extend T::Sig
4
+
3
5
  def self.instance
4
6
  @matcher_registry ||= new
5
7
  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
- Signature = Struct.new(
3
- :positional_params,
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
- Params = Struct.new(
18
- :all,
19
- :required,
20
- :optional,
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