mocktail 2.0.0 → 3.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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +2 -2
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +3 -2
  5. data/Gemfile.lock +76 -82
  6. data/README.md +2 -0
  7. data/docs/code_of_conduct.md +8 -0
  8. data/docs/configuring_minitest.md +20 -0
  9. data/docs/configuring_rspec.md +19 -0
  10. data/docs/example_test.md +594 -0
  11. data/docs/faq/existing_tests.md +47 -0
  12. data/docs/faq/mocking_http.md +151 -0
  13. data/docs/faq/mocking_the_subject.md +161 -0
  14. data/docs/faq/mocking_time.md +34 -0
  15. data/docs/faq/partial_mocks.md +34 -0
  16. data/docs/faq/verifying_real_interactions.md +52 -0
  17. data/docs/img/delegator_tree.png +0 -0
  18. data/docs/img/example_test.png +0 -0
  19. data/docs/img/extract_transform_load.png +0 -0
  20. data/docs/img/mocktail_sorbet.jpg +0 -0
  21. data/docs/img/mocktail_untyped.jpg +0 -0
  22. data/docs/img/spacer.png +0 -0
  23. data/docs/installation_sorbet.md +99 -0
  24. data/docs/installation_untyped.md +29 -0
  25. data/docs/other_uses.md +32 -0
  26. data/docs/stubbing_and_verifying.md +515 -0
  27. data/docs/support/api.md +819 -0
  28. data/docs/support/example_test.rb +94 -0
  29. data/docs/support/glossary.md +426 -0
  30. data/docs/tdd/class_methods.md +44 -0
  31. data/docs/tdd/poro/dependency_inception.md +136 -0
  32. data/docs/tdd/poro/dependency_injection.md +82 -0
  33. data/docs/tdd/poro.md +15 -0
  34. data/docs/tdd/third_party.md +220 -0
  35. data/docs/tdd.md +15 -0
  36. data/lib/mocktail/debug.rb +4 -3
  37. data/lib/mocktail/share/stringifies_call.rb +0 -1
  38. data/lib/mocktail/simulates_argument_error/transforms_params.rb +2 -2
  39. data/lib/mocktail/sorbet/mocktail/debug.rb +4 -3
  40. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +0 -1
  41. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +2 -2
  42. data/lib/mocktail/sorbet/mocktail/value/call.rb +1 -1
  43. data/lib/mocktail/sorbet/mocktail/version.rb +1 -1
  44. data/lib/mocktail/value/call.rb +1 -1
  45. data/lib/mocktail/version.rb +1 -1
  46. data/mocktail.gemspec +2 -2
  47. data/rbi/mocktail-pregenerated.rbi +58 -0
  48. data/script/build +47 -0
  49. data/script/setup +17 -0
  50. data/script/spoom_me +7 -0
  51. data/script/strip_sigils +17 -0
  52. data/script/test +33 -0
  53. data/script/test_double_require_warnings +46 -0
  54. data/script/update +21 -0
  55. data/sorbet/config +12 -0
  56. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  57. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  58. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  59. data/sorbet/rbi/gems/docile@1.4.0.rbi +376 -0
  60. data/sorbet/rbi/gems/json@2.6.3.rbi +1533 -0
  61. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  62. data/sorbet/rbi/gems/lint_roller@1.0.0.rbi +230 -0
  63. data/sorbet/rbi/gems/m@1.6.1.rbi +257 -0
  64. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  65. data/sorbet/rbi/gems/minitest@5.18.0.rbi +1491 -0
  66. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  67. data/sorbet/rbi/gems/parallel@1.23.0.rbi +273 -0
  68. data/sorbet/rbi/gems/parser@3.2.2.1.rbi +7253 -0
  69. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  70. data/sorbet/rbi/gems/rake@13.0.6.rbi +3024 -0
  71. data/sorbet/rbi/gems/rbi@0.0.16.rbi +3049 -0
  72. data/sorbet/rbi/gems/regexp_parser@2.8.0.rbi +3748 -0
  73. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
  74. data/sorbet/rbi/gems/rubocop-ast@1.29.0.rbi +7003 -0
  75. data/sorbet/rbi/gems/rubocop-performance@1.18.0.rbi +8 -0
  76. data/sorbet/rbi/gems/rubocop-sorbet@0.7.0.rbi +1043 -0
  77. data/sorbet/rbi/gems/rubocop@1.52.0.rbi +55500 -0
  78. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  79. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +216 -0
  80. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2148 -0
  81. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +8 -0
  82. data/sorbet/rbi/gems/spoom@1.2.1.rbi +2536 -0
  83. data/sorbet/rbi/gems/standard-custom@1.0.1.rbi +63 -0
  84. data/sorbet/rbi/gems/standard-performance@1.1.0.rbi +8 -0
  85. data/sorbet/rbi/gems/standard@1.29.0.rbi +850 -0
  86. data/sorbet/rbi/gems/tapioca@0.11.6.rbi +3301 -0
  87. data/sorbet/rbi/gems/thor@1.2.2.rbi +3965 -0
  88. data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
  89. data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
  90. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  91. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  92. data/sorbet/tapioca/config.yml +13 -0
  93. data/sorbet/tapioca/require.rb +4 -0
  94. data/spoom_data/05f0c49.json +1 -0
  95. data/spoom_data/1657012.json +1 -0
  96. data/spoom_data/167434b.json +1 -0
  97. data/spoom_data/1d0ba5b.json +1 -0
  98. data/spoom_data/1ffa724.json +1 -0
  99. data/spoom_data/2129b3d.json +1 -0
  100. data/spoom_data/24b1c92.json +1 -0
  101. data/spoom_data/2c46aee.json +1 -0
  102. data/spoom_data/305ec0b.json +1 -0
  103. data/spoom_data/30e9528.json +1 -0
  104. data/spoom_data/4638cd5.json +1 -0
  105. data/spoom_data/47c7dad.json +1 -0
  106. data/spoom_data/4b1edef.json +1 -0
  107. data/spoom_data/4de157f.json +1 -0
  108. data/spoom_data/526e7db.json +1 -0
  109. data/spoom_data/5d093b9.json +1 -0
  110. data/spoom_data/5db3b43.json +1 -0
  111. data/spoom_data/5fe2a65.json +1 -0
  112. data/spoom_data/6891312.json +1 -0
  113. data/spoom_data/6b0fef4.json +1 -0
  114. data/spoom_data/6b83d12.json +1 -0
  115. data/spoom_data/74c83c2.json +1 -0
  116. data/spoom_data/7644ff4.json +1 -0
  117. data/spoom_data/79054db.json +1 -0
  118. data/spoom_data/814e515.json +1 -0
  119. data/spoom_data/88c3b60.json +1 -0
  120. data/spoom_data/8bd4b6e.json +1 -0
  121. data/spoom_data/93f8153.json +1 -0
  122. data/spoom_data/95242fe.json +1 -0
  123. data/spoom_data/97f4c09.json +1 -0
  124. data/spoom_data/a13d150.json +1 -0
  125. data/spoom_data/a17f215.json +1 -0
  126. data/spoom_data/b705a9d.json +1 -0
  127. data/spoom_data/ba19195.json +1 -0
  128. data/spoom_data/bef51ca.json +1 -0
  129. data/spoom_data/dbb595a.json +1 -0
  130. data/spoom_data/e8fab92.json +1 -0
  131. data/spoom_data/f166c87.json +1 -0
  132. data/spoom_data/f57992d.json +1 -0
  133. data/spoom_data/f5a1e40.json +1 -0
  134. data/spoom_data/f6d6431.json +1 -0
  135. data/spoom_data/f72b67c.json +1 -0
  136. data/spoom_data/fc2f231.json +1 -0
  137. data/src/mocktail/collects_calls.rb +18 -0
  138. data/src/mocktail/debug.rb +55 -0
  139. data/src/mocktail/dsl.rb +46 -0
  140. data/src/mocktail/errors.rb +19 -0
  141. data/src/mocktail/explains_nils.rb +41 -0
  142. data/src/mocktail/explains_thing.rb +137 -0
  143. data/src/mocktail/grabs_original_method_parameters.rb +33 -0
  144. data/src/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  145. data/src/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  146. data/src/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  147. data/src/mocktail/handles_dry_call/logs_call.rb +12 -0
  148. data/src/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  149. data/src/mocktail/handles_dry_call.rb +25 -0
  150. data/src/mocktail/handles_dry_new_call.rb +42 -0
  151. data/src/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  152. data/src/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  153. data/src/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  154. data/src/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  155. data/src/mocktail/imitates_type/makes_double.rb +29 -0
  156. data/src/mocktail/imitates_type.rb +29 -0
  157. data/src/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  158. data/src/mocktail/initializes_mocktail.rb +25 -0
  159. data/src/mocktail/matcher_presentation.rb +21 -0
  160. data/src/mocktail/matchers/any.rb +27 -0
  161. data/src/mocktail/matchers/base.rb +39 -0
  162. data/src/mocktail/matchers/captor.rb +76 -0
  163. data/src/mocktail/matchers/includes.rb +32 -0
  164. data/src/mocktail/matchers/includes_hash.rb +12 -0
  165. data/src/mocktail/matchers/includes_key.rb +12 -0
  166. data/src/mocktail/matchers/includes_string.rb +12 -0
  167. data/src/mocktail/matchers/is_a.rb +17 -0
  168. data/src/mocktail/matchers/matches.rb +19 -0
  169. data/src/mocktail/matchers/not.rb +17 -0
  170. data/src/mocktail/matchers/numeric.rb +27 -0
  171. data/src/mocktail/matchers/that.rb +32 -0
  172. data/src/mocktail/matchers.rb +19 -0
  173. data/src/mocktail/raises_neato_no_method_error.rb +93 -0
  174. data/src/mocktail/records_demonstration.rb +43 -0
  175. data/src/mocktail/registers_matcher.rb +65 -0
  176. data/src/mocktail/registers_stubbing.rb +31 -0
  177. data/src/mocktail/replaces_next.rb +55 -0
  178. data/src/mocktail/replaces_type/redefines_new.rb +32 -0
  179. data/src/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  180. data/src/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  181. data/src/mocktail/replaces_type.rb +36 -0
  182. data/src/mocktail/resets_state.rb +14 -0
  183. data/src/mocktail/share/bind.rb +18 -0
  184. data/src/mocktail/share/cleans_backtrace.rb +22 -0
  185. data/src/mocktail/share/creates_identifier.rb +39 -0
  186. data/src/mocktail/share/determines_matching_calls.rb +72 -0
  187. data/src/mocktail/share/stringifies_call.rb +84 -0
  188. data/src/mocktail/share/stringifies_method_name.rb +16 -0
  189. data/src/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  190. data/src/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  191. data/src/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  192. data/src/mocktail/simulates_argument_error.rb +36 -0
  193. data/src/mocktail/sorbet.rb +3 -0
  194. data/src/mocktail/stringifies_method_signature.rb +53 -0
  195. data/src/mocktail/typed.rb +5 -0
  196. data/src/mocktail/value/cabinet.rb +91 -0
  197. data/src/mocktail/value/call.rb +51 -0
  198. data/src/mocktail/value/demo_config.rb +10 -0
  199. data/src/mocktail/value/double.rb +10 -0
  200. data/src/mocktail/value/double_data.rb +15 -0
  201. data/src/mocktail/value/explanation.rb +68 -0
  202. data/src/mocktail/value/explanation_data.rb +19 -0
  203. data/src/mocktail/value/fake_method_data.rb +11 -0
  204. data/src/mocktail/value/matcher_registry.rb +27 -0
  205. data/src/mocktail/value/no_explanation_data.rb +20 -0
  206. data/src/mocktail/value/signature.rb +35 -0
  207. data/src/mocktail/value/stubbing.rb +26 -0
  208. data/src/mocktail/value/top_shelf.rb +79 -0
  209. data/src/mocktail/value/type_replacement.rb +11 -0
  210. data/src/mocktail/value/type_replacement_data.rb +19 -0
  211. data/src/mocktail/value/unsatisfying_call.rb +9 -0
  212. data/src/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  213. data/src/mocktail/value.rb +19 -0
  214. data/src/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  215. data/src/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  216. data/src/mocktail/verifies_call/raises_verification_error.rb +74 -0
  217. data/src/mocktail/verifies_call.rb +37 -0
  218. data/src/mocktail/version.rb +12 -0
  219. data/src/mocktail.rb +154 -0
  220. data/sub_projects/rbi_generator/Gemfile +4 -0
  221. data/sub_projects/rbi_generator/Gemfile.lock +63 -0
  222. data/sub_projects/rbi_generator/sorbet/rbi/gems/.gitattributes +1 -0
  223. data/sub_projects/sorbet_user/Gemfile +13 -0
  224. data/sub_projects/sorbet_user/Gemfile.lock +126 -0
  225. data/sub_projects/sorbet_user/Rakefile +10 -0
  226. data/sub_projects/sorbet_user/bin/tapioca +27 -0
  227. data/sub_projects/sorbet_user/rbi/mocktail.rbi +9 -0
  228. data/sub_projects/sorbet_user/script/test +13 -0
  229. data/sub_projects/sorbet_user/sorbet/config +4 -0
  230. data/sub_projects/sorbet_user/sorbet/rbi/gems/.gitattributes +1 -0
  231. data/sub_projects/sorbet_user/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
  232. data/sub_projects/sorbet_user/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  233. data/sub_projects/sorbet_user/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  234. data/sub_projects/sorbet_user/sorbet/rbi/gems/json@2.21.2.rbi +2140 -0
  235. data/sub_projects/sorbet_user/sorbet/rbi/gems/language_server-protocol@3.17.0.4.rbi +14244 -0
  236. data/sub_projects/sorbet_user/sorbet/rbi/gems/lint_roller@1.1.0.rbi +239 -0
  237. data/sub_projects/sorbet_user/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
  238. data/sub_projects/sorbet_user/sorbet/rbi/gems/m@1.6.2.rbi +258 -0
  239. data/sub_projects/sorbet_user/sorbet/rbi/gems/method_source@1.1.0.rbi +9 -0
  240. data/sub_projects/sorbet_user/sorbet/rbi/gems/minitest@5.27.0.rbi +1549 -0
  241. data/sub_projects/sorbet_user/sorbet/rbi/gems/mocktail@3.0.0.rbi +1923 -0
  242. data/sub_projects/sorbet_user/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  243. data/sub_projects/sorbet_user/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  244. data/sub_projects/sorbet_user/sorbet/rbi/gems/parser@3.3.7.4.rbi +5451 -0
  245. data/sub_projects/sorbet_user/sorbet/rbi/gems/prism@1.4.0.rbi +41732 -0
  246. data/sub_projects/sorbet_user/sorbet/rbi/gems/racc@1.8.1.rbi +164 -0
  247. data/sub_projects/sorbet_user/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  248. data/sub_projects/sorbet_user/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
  249. data/sub_projects/sorbet_user/sorbet/rbi/gems/rbi@0.3.1.rbi +6599 -0
  250. data/sub_projects/sorbet_user/sorbet/rbi/gems/rbs@3.9.2.rbi +6976 -0
  251. data/sub_projects/sorbet_user/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3795 -0
  252. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-ast@1.43.0.rbi +7717 -0
  253. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-performance@1.24.0.rbi +9 -0
  254. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-sorbet@0.9.0.rbi +9 -0
  255. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop@1.73.2.rbi +61306 -0
  256. data/sub_projects/sorbet_user/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  257. data/sub_projects/sorbet_user/sorbet/rbi/gems/sorbet-eraser@0.3.1.rbi +8 -0
  258. data/sub_projects/sorbet_user/sorbet/rbi/gems/spoom@1.6.1.rbi +7274 -0
  259. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-custom@1.0.2.rbi +8 -0
  260. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-performance@1.7.0.rbi +9 -0
  261. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-sorbet@0.0.3.rbi +53 -0
  262. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard@1.47.0.rbi +935 -0
  263. data/sub_projects/sorbet_user/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
  264. data/sub_projects/sorbet_user/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  265. data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
  266. data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +254 -0
  267. data/sub_projects/sorbet_user/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  268. data/sub_projects/sorbet_user/sorbet/rbi/gems/yard@0.9.45.rbi +18916 -0
  269. data/sub_projects/sorbet_user/sorbet/tapioca/config.yml +13 -0
  270. data/sub_projects/sorbet_user/sorbet/tapioca/require.rb +4 -0
  271. data/sub_projects/sorbet_user/test/ensure_type_safety_test.rb +21 -0
  272. data/sub_projects/sorbet_user/test/paint_by_number_test.rb +113 -0
  273. data/sub_projects/sorbet_user/test/sorbet_test.rb +289 -0
  274. data/sub_projects/sorbet_user/test/test_helper.rb +35 -0
  275. data/sub_projects/untyped_user/Gemfile +4 -0
  276. data/sub_projects/untyped_user/Gemfile.lock +25 -0
  277. data/sub_projects/untyped_user/antitype_test.rb +54 -0
  278. data/sub_projects/untyped_user/script/test +5 -0
  279. metadata +264 -8
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+
3
+ require_relative "fulfills_stubbing/finds_satisfaction"
4
+ require_relative "fulfills_stubbing/describes_unsatisfied_stubbing"
5
+
6
+ module Mocktail
7
+ class FulfillsStubbing
8
+ extend T::Sig
9
+
10
+ sig { void }
11
+ def initialize
12
+ @finds_satisfaction = T.let(FindsSatisfaction.new, Mocktail::FindsSatisfaction)
13
+ @describes_unsatisfied_stubbing = T.let(DescribesUnsatisfiedStubbing.new, Mocktail::DescribesUnsatisfiedStubbing)
14
+ end
15
+
16
+ sig { params(dry_call: Call).returns(T.anything) }
17
+ def fulfill(dry_call)
18
+ if (stubbing = satisfaction(dry_call))
19
+ stubbing.satisfied!
20
+ stubbing.effect&.call(dry_call)
21
+ else
22
+ store_unsatisfying_call!(dry_call)
23
+ nil
24
+ end
25
+ end
26
+
27
+ sig { params(dry_call: Call).returns(T.nilable(Stubbing[T.anything])) }
28
+ def satisfaction(dry_call)
29
+ return if Mocktail.cabinet.demonstration_in_progress?
30
+
31
+ @finds_satisfaction.find(dry_call)
32
+ end
33
+
34
+ private
35
+
36
+ sig { params(dry_call: Call).void }
37
+ def store_unsatisfying_call!(dry_call)
38
+ return if Mocktail.cabinet.demonstration_in_progress?
39
+
40
+ Mocktail.cabinet.store_unsatisfying_call(
41
+ @describes_unsatisfied_stubbing.describe(dry_call)
42
+ )
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class LogsCall
5
+ extend T::Sig
6
+
7
+ sig { params(dry_call: Call).void }
8
+ def log(dry_call)
9
+ Mocktail.cabinet.store_call(dry_call)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class ValidatesArguments
5
+ extend T::Sig
6
+ sig { void }
7
+ def self.disable!
8
+ Thread.current[:mocktail_arity_validation_disabled] = true
9
+ end
10
+
11
+ sig { void }
12
+ def self.enable!
13
+ Thread.current[:mocktail_arity_validation_disabled] = false
14
+ end
15
+
16
+ sig { returns(T::Boolean) }
17
+ def self.disabled?
18
+ !!Thread.current[:mocktail_arity_validation_disabled]
19
+ end
20
+
21
+ sig { params(disable: T.nilable(T::Boolean), blk: T.proc.returns(T.anything)).void }
22
+ def self.optional(disable, &blk)
23
+ return blk.call unless disable
24
+
25
+ disable!
26
+ ret = blk.call
27
+ enable!
28
+ ret
29
+ end
30
+
31
+ sig { void }
32
+ def initialize
33
+ @simulates_argument_error = T.let(SimulatesArgumentError.new, Mocktail::SimulatesArgumentError)
34
+ end
35
+
36
+ sig { params(dry_call: Call).returns(NilClass) }
37
+ def validate(dry_call)
38
+ return if self.class.disabled?
39
+
40
+ if (error = @simulates_argument_error.simulate(dry_call))
41
+ raise error
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ require_relative "handles_dry_call/fulfills_stubbing"
4
+ require_relative "handles_dry_call/logs_call"
5
+ require_relative "handles_dry_call/validates_arguments"
6
+
7
+ module Mocktail
8
+ class HandlesDryCall
9
+ extend T::Sig
10
+
11
+ sig { void }
12
+ def initialize
13
+ @validates_arguments = T.let(ValidatesArguments.new, ValidatesArguments)
14
+ @logs_call = T.let(LogsCall.new, LogsCall)
15
+ @fulfills_stubbing = T.let(FulfillsStubbing.new, FulfillsStubbing)
16
+ end
17
+
18
+ sig { params(dry_call: Call).returns(T.anything) }
19
+ def handle(dry_call)
20
+ @validates_arguments.validate(dry_call)
21
+ @logs_call.log(dry_call)
22
+ @fulfills_stubbing.fulfill(dry_call)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class HandlesDryNewCall
5
+ extend T::Sig
6
+
7
+ sig { void }
8
+ def initialize
9
+ @validates_arguments = T.let(ValidatesArguments.new, ValidatesArguments)
10
+ @logs_call = T.let(LogsCall.new, LogsCall)
11
+ @fulfills_stubbing = T.let(FulfillsStubbing.new, FulfillsStubbing)
12
+ @imitates_type = T.let(ImitatesType.new, ImitatesType)
13
+ end
14
+
15
+ sig { params(type: T::Class[T.all(T, Object)], args: T::Array[T.anything], kwargs: T::Hash[Symbol, T.anything], block: T.nilable(Proc)).returns(T.anything) }
16
+ def handle(type, args, kwargs, block)
17
+ @validates_arguments.validate(Call.new(
18
+ original_method: type.instance_method(:initialize),
19
+ args: args,
20
+ kwargs: kwargs,
21
+ block: block
22
+ ))
23
+
24
+ new_call = Call.new(
25
+ singleton: true,
26
+ double: type,
27
+ original_type: type,
28
+ dry_type: type,
29
+ method: :new,
30
+ args: args,
31
+ kwargs: kwargs,
32
+ block: block
33
+ )
34
+ @logs_call.log(new_call)
35
+ if @fulfills_stubbing.satisfaction(new_call)
36
+ @fulfills_stubbing.fulfill(new_call)
37
+ else
38
+ @imitates_type.imitate(type)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class EnsuresImitationSupport
5
+ extend T::Sig
6
+
7
+ sig { params(type: T.any(T::Class[T.anything], Module)).void }
8
+ def ensure(type)
9
+ unless type.is_a?(Class) || type.is_a?(Module)
10
+ raise UnsupportedMocktail.new <<~MSG.tr("\n", " ")
11
+ Mocktail.of() can only mix mocktail instances of modules and classes.
12
+ MSG
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,73 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class ReconstructsCall
5
+ extend T::Sig
6
+
7
+ sig {
8
+ params(
9
+ double: Object,
10
+ call_binding: Binding,
11
+ default_args: T.nilable(T::Hash[Symbol, T.anything]),
12
+ dry_class: T::Class[Object],
13
+ type: T.any(Module, T::Class[T.anything]),
14
+ method: Symbol,
15
+ original_method: T.any(UnboundMethod, Method),
16
+ signature: Signature
17
+ ).returns(Call)
18
+ }
19
+ def reconstruct(double:, call_binding:, default_args:, dry_class:, type:, method:, original_method:, signature:)
20
+ Call.new(
21
+ singleton: false,
22
+ double: double,
23
+ original_type: type,
24
+ dry_type: dry_class,
25
+ method: method,
26
+ original_method: original_method,
27
+ args: args_for(signature, call_binding, default_args),
28
+ kwargs: kwargs_for(signature, call_binding, default_args),
29
+ block: call_binding.local_variable_get(signature.block_param || ::Mocktail::Signature::DEFAULT_BLOCK_PARAM)
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ sig {
36
+ params(signature: Signature, call_binding: Binding, default_args: T.nilable(T::Hash[Symbol, T.anything]))
37
+ .returns(T::Array[T.anything])
38
+ }
39
+ def args_for(signature, call_binding, default_args)
40
+ arg_names, rest_name = non_default_args(signature.positional_params, default_args)
41
+
42
+ arg_values = arg_names.map { |p| call_binding.local_variable_get(p) }
43
+ rest_value = call_binding.local_variable_get(rest_name) if rest_name
44
+
45
+ arg_values + (rest_value || [])
46
+ end
47
+
48
+ sig {
49
+ params(signature: Signature, call_binding: Binding, default_args: T.nilable(T::Hash[Symbol, T.anything]))
50
+ .returns(T::Hash[Symbol, T.anything])
51
+ }
52
+ def kwargs_for(signature, call_binding, default_args)
53
+ kwarg_names, kwrest_name = non_default_args(signature.keyword_params, default_args)
54
+
55
+ kwarg_values = kwarg_names.to_h { |p| [p, call_binding.local_variable_get(p)] }
56
+ kwrest_value = call_binding.local_variable_get(kwrest_name) if kwrest_name
57
+
58
+ kwarg_values.merge(kwrest_value || {})
59
+ end
60
+
61
+ sig { params(params: Params, default_args: T.nilable(T::Hash[Symbol, T.anything])).returns([T::Array[Symbol], T.nilable(Symbol)]) }
62
+ def non_default_args(params, default_args)
63
+ named_args = params.allowed
64
+ .reject { |p| default_args&.key?(p) }
65
+ rest_param = params.rest
66
+ rest_arg = if rest_param && !default_args&.key?(rest_param)
67
+ params.rest
68
+ end
69
+
70
+ [named_args, rest_arg]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,136 @@
1
+ # typed: strict
2
+
3
+ require_relative "declares_dry_class/reconstructs_call"
4
+
5
+ module Mocktail
6
+ class DeclaresDryClass
7
+ extend T::Sig
8
+
9
+ DEFAULT_ANCESTORS = T.let(T.must(Class.new(Object).ancestors[1..]), T::Array[T.any(T::Class[T.anything], Module)])
10
+
11
+ sig { void }
12
+ def initialize
13
+ @raises_neato_no_method_error = T.let(RaisesNeatoNoMethodError.new, RaisesNeatoNoMethodError)
14
+ @transforms_params = T.let(TransformsParams.new, TransformsParams)
15
+ @stringifies_method_signature = T.let(StringifiesMethodSignature.new, StringifiesMethodSignature)
16
+ @grabs_original_method_parameters = T.let(GrabsOriginalMethodParameters.new, GrabsOriginalMethodParameters)
17
+ end
18
+
19
+ sig {
20
+ type_parameters(:T)
21
+ .params(type: T.all(T.type_parameter(:T), T::Class[Object]), instance_methods: T::Array[Symbol]).returns(T.type_parameter(:T))
22
+ }
23
+ def declare(type, instance_methods)
24
+ dry_class = Class.new(Object) {
25
+ include type if T.unsafe(type).is_a?(Module) && !T.unsafe(type).is_a?(Class)
26
+
27
+ define_method :initialize do |*args, **kwargs, &blk|
28
+ end
29
+
30
+ [:is_a?, :kind_of?].each do |method_name|
31
+ define_method method_name, ->(thing) {
32
+ # Mocktails extend from Object, so share the same ancestors, plus the passed type
33
+ [type, *DEFAULT_ANCESTORS].include?(thing)
34
+ }
35
+ end
36
+
37
+ if T.unsafe(type).is_a?(Class)
38
+ define_method :instance_of?, ->(thing) {
39
+ type == thing
40
+ }
41
+ end
42
+ }
43
+
44
+ add_more_methods!(dry_class, type, instance_methods)
45
+
46
+ T.unsafe(dry_class) # This is all fake! That's the whole point—it's not a real Foo, it's just some new class that quacks like a Foo
47
+ end
48
+
49
+ private
50
+
51
+ # These have special implementations, but if the user defines
52
+ # any of them on the object itself, then they'll be replaced with normal
53
+ # mocked methods. YMMV
54
+ sig { params(dry_class: T::Class[Object], type: T.any(T::Class[T.anything], Module), instance_methods: T::Array[Symbol]).void }
55
+ def add_more_methods!(dry_class, type, instance_methods)
56
+ add_stringify_methods!(dry_class, :to_s, type, instance_methods)
57
+ add_stringify_methods!(dry_class, :inspect, type, instance_methods)
58
+ define_method_missing_errors!(dry_class, type, instance_methods)
59
+
60
+ define_double_methods!(dry_class, type, instance_methods)
61
+ end
62
+
63
+ sig { params(dry_class: T::Class[Object], type: T.any(T::Class[T.anything], Module), instance_methods: T::Array[Symbol]).void }
64
+ def define_double_methods!(dry_class, type, instance_methods)
65
+ instance_methods.each do |method_name|
66
+ dry_class.undef_method(method_name) if dry_class.method_defined?(method_name)
67
+ parameters = @grabs_original_method_parameters.grab(type.instance_method(method_name))
68
+ signature = @transforms_params.transform(Call.new, params: parameters)
69
+ method_signature = @stringifies_method_signature.stringify(signature)
70
+ __mocktail_closure = {
71
+ dry_class: dry_class,
72
+ type: type,
73
+ method: method_name,
74
+ original_method: type.instance_method(method_name),
75
+ signature: signature
76
+ }
77
+
78
+ dry_class.define_method method_name,
79
+ eval(<<-RUBBY, binding, __FILE__, __LINE__ + 1) # standard:disable Security/Eval
80
+ ->#{method_signature} do
81
+ ::Mocktail::Debug.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
82
+ ::Mocktail::HandlesDryCall.new.handle(::Mocktail::ReconstructsCall.new.reconstruct(
83
+ double: self,
84
+ call_binding: __send__(:binding),
85
+ default_args: (__send__(:binding).local_variable_defined?(:__mocktail_default_args) ? __send__(:binding).local_variable_get(:__mocktail_default_args) : {}),
86
+ **__mocktail_closure
87
+ ))
88
+ end
89
+ RUBBY
90
+ end
91
+ end
92
+
93
+ sig { params(dry_class: T::Class[Object], method_name: Symbol, type: T.any(T::Class[T.anything], Module), instance_methods: T::Array[Symbol]).void }
94
+ def add_stringify_methods!(dry_class, method_name, type, instance_methods)
95
+ dry_class.define_singleton_method method_name, -> {
96
+ if (id_matches = super().match(/:([0-9a-fx]+)>$/))
97
+ "#<Class #{"including module " if type.instance_of?(Module)}for mocktail of #{type.name}:#{id_matches[1]}>"
98
+ else
99
+ super()
100
+ end
101
+ }
102
+
103
+ unless instance_methods.include?(method_name)
104
+ dry_class.define_method method_name, -> {
105
+ if (id_matches = super().match(/:([0-9a-fx]+)>$/))
106
+ "#<Mocktail of #{type.name}:#{id_matches[1]}>"
107
+ else
108
+ super()
109
+ end
110
+ }
111
+ end
112
+ end
113
+
114
+ sig { params(dry_class: T::Class[Object], type: T.any(T::Class[T.anything], Module), instance_methods: T::Array[Symbol]).void }
115
+ def define_method_missing_errors!(dry_class, type, instance_methods)
116
+ return if instance_methods.include?(:method_missing)
117
+
118
+ raises_neato_no_method_error = @raises_neato_no_method_error
119
+ dry_class.define_method :method_missing, ->(name, *args, **kwargs, &block) {
120
+ raises_neato_no_method_error.call(
121
+ Call.new(
122
+ singleton: false,
123
+ double: self,
124
+ original_type: type,
125
+ dry_type: self.class,
126
+ method: name,
127
+ original_method: nil,
128
+ args: args,
129
+ kwargs: kwargs,
130
+ block: block
131
+ )
132
+ )
133
+ }
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class GathersFakeableInstanceMethods
5
+ extend T::Sig
6
+
7
+ sig { params(type: T.any(T::Class[T.anything], Module)).returns(T::Array[Symbol]) }
8
+ def gather(type)
9
+ methods = type.instance_methods + [
10
+ (:respond_to_missing? if type.private_method_defined?(:respond_to_missing?))
11
+ ].compact
12
+
13
+ methods.reject { |m|
14
+ ignore?(type, m)
15
+ }
16
+ end
17
+
18
+ sig { params(type: T.any(T::Class[T.anything], Module), method_name: Symbol).returns(T::Boolean) }
19
+ def ignore?(type, method_name)
20
+ ignored_ancestors.include?(type.instance_method(method_name).owner)
21
+ end
22
+
23
+ sig { returns(T::Array[Module]) }
24
+ def ignored_ancestors
25
+ Object.ancestors
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+
3
+ require_relative "makes_double/declares_dry_class"
4
+ require_relative "makes_double/gathers_fakeable_instance_methods"
5
+
6
+ module Mocktail
7
+ class MakesDouble
8
+ extend T::Sig
9
+
10
+ sig { void }
11
+ def initialize
12
+ @declares_dry_class = T.let(DeclaresDryClass.new, DeclaresDryClass)
13
+ @gathers_fakeable_instance_methods = T.let(GathersFakeableInstanceMethods.new, GathersFakeableInstanceMethods)
14
+ end
15
+
16
+ sig { params(type: T::Class[Object]).returns(Double) }
17
+ def make(type)
18
+ dry_methods = @gathers_fakeable_instance_methods.gather(type)
19
+ dry_type = @declares_dry_class.declare(type, dry_methods)
20
+
21
+ Double.new(
22
+ original_type: type,
23
+ dry_type: dry_type,
24
+ dry_instance: dry_type.new,
25
+ dry_methods: dry_methods
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+
3
+ require_relative "imitates_type/ensures_imitation_support"
4
+ require_relative "imitates_type/makes_double"
5
+
6
+ module Mocktail
7
+ class ImitatesType
8
+ extend T::Sig
9
+ extend T::Generic
10
+
11
+ sig { void }
12
+ def initialize
13
+ @ensures_imitation_support = T.let(EnsuresImitationSupport.new, EnsuresImitationSupport)
14
+ @makes_double = T.let(MakesDouble.new, MakesDouble)
15
+ end
16
+
17
+ sig {
18
+ type_parameters(:T)
19
+ .params(type: T::Class[T.all(T.type_parameter(:T), Object)])
20
+ .returns(T.all(T.type_parameter(:T), Object))
21
+ }
22
+ def imitate(type)
23
+ @ensures_imitation_support.ensure(type)
24
+ T.cast(@makes_double.make(type).tap do |double|
25
+ Mocktail.cabinet.store_double(double)
26
+ end.dry_instance, T.all(T.type_parameter(:T), Object))
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # typed: false
2
+
3
+ require_relative "typed"
4
+
5
+ # Constant boolean, so won't statically type-check, but `T.unsafe` can't be used
6
+ # because we haven't required sorbet-runtime yet
7
+ if eval("Mocktail::TYPED", binding, __FILE__, __LINE__)
8
+ require "sorbet-runtime"
9
+ else
10
+ require "#{Gem.loaded_specs["sorbet-eraser"].gem_dir}/lib/t"
11
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class InitializesMocktail
5
+ extend T::Sig
6
+
7
+ sig { void }
8
+ def init
9
+ [
10
+ Mocktail::Matchers::Any,
11
+ Mocktail::Matchers::Includes,
12
+ Mocktail::Matchers::IncludesString,
13
+ Mocktail::Matchers::IncludesKey,
14
+ Mocktail::Matchers::IncludesHash,
15
+ Mocktail::Matchers::IsA,
16
+ Mocktail::Matchers::Matches,
17
+ Mocktail::Matchers::Not,
18
+ Mocktail::Matchers::Numeric,
19
+ Mocktail::Matchers::That
20
+ ].each do |matcher_type|
21
+ Mocktail.register_matcher(matcher_type)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class MatcherPresentation
5
+ extend T::Sig
6
+
7
+ sig { params(name: Symbol, include_private: T::Boolean).returns(T::Boolean) }
8
+ def respond_to_missing?(name, include_private = false)
9
+ !!MatcherRegistry.instance.get(name) || super
10
+ end
11
+
12
+ sig { params(name: Symbol, args: T.anything, kwargs: T.anything, blk: T.nilable(Proc)).returns(T.anything) }
13
+ def method_missing(name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
14
+ if (matcher = MatcherRegistry.instance.get(name))
15
+ T.unsafe(matcher).new(*args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class Any < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :any
10
+ end
11
+
12
+ sig { void }
13
+ def initialize
14
+ # Empty initialize is necessary b/c Base default expects an argument
15
+ end
16
+
17
+ sig { params(actual: T.anything).returns(T::Boolean) }
18
+ def match?(actual)
19
+ true
20
+ end
21
+
22
+ sig { returns(String) }
23
+ def inspect
24
+ "any"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class Base
5
+ extend T::Sig
6
+ extend T::Helpers
7
+
8
+ if T.unsafe(Mocktail::TYPED) && T::Private::RuntimeLevels.default_checked_level != :never
9
+ abstract!
10
+ end
11
+
12
+ # Custom matchers can receive any args, kwargs, or block they want. Usually
13
+ # single-argument, though, so that's defaulted here and in #insepct
14
+ sig { params(expected: BasicObject).void }
15
+ def initialize(expected)
16
+ @expected = expected
17
+ end
18
+
19
+ sig { returns(Symbol) }
20
+ def self.matcher_name
21
+ raise Mocktail::InvalidMatcherError.new("The `matcher_name` class method must return a valid method name")
22
+ end
23
+
24
+ sig { params(actual: T.untyped).returns(T::Boolean) }
25
+ def match?(actual)
26
+ raise Mocktail::InvalidMatcherError.new("Matchers must implement `match?(argument)`")
27
+ end
28
+
29
+ sig { returns(String) }
30
+ def inspect
31
+ "#{self.class.matcher_name}(#{T.cast(@expected, Object).inspect})"
32
+ end
33
+
34
+ sig { returns(TrueClass) }
35
+ def is_mocktail_matcher?
36
+ true
37
+ end
38
+ end
39
+ end