mocktail 1.2.3 → 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 (443) 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 +19 -0
  6. data/Gemfile +8 -2
  7. data/Gemfile.lock +105 -38
  8. data/README.md +20 -922
  9. data/Rakefile +0 -1
  10. data/bin/console +1 -2
  11. data/bin/tapioca +29 -0
  12. data/docs/code_of_conduct.md +8 -0
  13. data/docs/configuring_minitest.md +20 -0
  14. data/docs/configuring_rspec.md +19 -0
  15. data/docs/example_test.md +594 -0
  16. data/docs/faq/existing_tests.md +47 -0
  17. data/docs/faq/mocking_http.md +151 -0
  18. data/docs/faq/mocking_the_subject.md +161 -0
  19. data/docs/faq/mocking_time.md +34 -0
  20. data/docs/faq/partial_mocks.md +34 -0
  21. data/docs/faq/verifying_real_interactions.md +52 -0
  22. data/docs/img/delegator_tree.png +0 -0
  23. data/docs/img/example_test.png +0 -0
  24. data/docs/img/extract_transform_load.png +0 -0
  25. data/docs/img/mocktail_sorbet.jpg +0 -0
  26. data/docs/img/mocktail_untyped.jpg +0 -0
  27. data/docs/img/spacer.png +0 -0
  28. data/docs/installation_sorbet.md +99 -0
  29. data/docs/installation_untyped.md +29 -0
  30. data/docs/other_uses.md +32 -0
  31. data/docs/stubbing_and_verifying.md +515 -0
  32. data/docs/support/api.md +819 -0
  33. data/docs/support/example_test.rb +94 -0
  34. data/docs/support/glossary.md +426 -0
  35. data/docs/tdd/class_methods.md +44 -0
  36. data/docs/tdd/poro/dependency_inception.md +136 -0
  37. data/docs/tdd/poro/dependency_injection.md +82 -0
  38. data/docs/tdd/poro.md +15 -0
  39. data/docs/tdd/third_party.md +220 -0
  40. data/docs/tdd.md +15 -0
  41. data/lib/mocktail/collects_calls.rb +2 -0
  42. data/lib/mocktail/debug.rb +16 -12
  43. data/lib/mocktail/dsl.rb +2 -0
  44. data/lib/mocktail/errors.rb +2 -0
  45. data/lib/mocktail/explains_nils.rb +2 -0
  46. data/lib/mocktail/explains_thing.rb +7 -4
  47. data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
  48. data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
  49. data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
  50. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
  51. data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
  52. data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
  53. data/lib/mocktail/handles_dry_call.rb +2 -0
  54. data/lib/mocktail/handles_dry_new_call.rb +2 -0
  55. data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
  56. data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
  57. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
  58. data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
  59. data/lib/mocktail/imitates_type/makes_double.rb +3 -0
  60. data/lib/mocktail/imitates_type.rb +3 -1
  61. data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
  62. data/lib/mocktail/initializes_mocktail.rb +5 -0
  63. data/lib/mocktail/matcher_presentation.rb +4 -2
  64. data/lib/mocktail/matchers/any.rb +4 -3
  65. data/lib/mocktail/matchers/base.rb +10 -2
  66. data/lib/mocktail/matchers/captor.rb +9 -0
  67. data/lib/mocktail/matchers/includes.rb +2 -0
  68. data/lib/mocktail/matchers/includes_hash.rb +9 -0
  69. data/lib/mocktail/matchers/includes_key.rb +9 -0
  70. data/lib/mocktail/matchers/includes_string.rb +9 -0
  71. data/lib/mocktail/matchers/is_a.rb +2 -0
  72. data/lib/mocktail/matchers/matches.rb +2 -0
  73. data/lib/mocktail/matchers/not.rb +2 -0
  74. data/lib/mocktail/matchers/numeric.rb +5 -4
  75. data/lib/mocktail/matchers/that.rb +2 -0
  76. data/lib/mocktail/matchers.rb +3 -0
  77. data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
  78. data/lib/mocktail/records_demonstration.rb +2 -0
  79. data/lib/mocktail/registers_matcher.rb +8 -3
  80. data/lib/mocktail/registers_stubbing.rb +2 -0
  81. data/lib/mocktail/replaces_next.rb +7 -1
  82. data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
  83. data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
  84. data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
  85. data/lib/mocktail/replaces_type.rb +6 -0
  86. data/lib/mocktail/resets_state.rb +2 -0
  87. data/lib/mocktail/share/bind.rb +7 -5
  88. data/lib/mocktail/share/cleans_backtrace.rb +3 -5
  89. data/lib/mocktail/share/creates_identifier.rb +16 -9
  90. data/lib/mocktail/share/determines_matching_calls.rb +4 -2
  91. data/lib/mocktail/share/stringifies_call.rb +6 -3
  92. data/lib/mocktail/share/stringifies_method_name.rb +3 -1
  93. data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
  94. data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
  95. data/lib/mocktail/simulates_argument_error/transforms_params.rb +16 -9
  96. data/lib/mocktail/simulates_argument_error.rb +2 -0
  97. data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
  98. data/lib/mocktail/sorbet/mocktail/debug.rb +55 -0
  99. data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
  100. data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
  101. data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
  102. data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
  103. data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
  104. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  105. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  106. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  107. data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
  108. data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  109. data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
  110. data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
  111. data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  112. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  113. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  114. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  115. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
  116. data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
  117. data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  118. data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
  119. data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
  120. data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
  121. data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
  122. data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
  123. data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
  124. data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
  125. data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
  126. data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
  127. data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
  128. data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
  129. data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
  130. data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
  131. data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
  132. data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
  133. data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
  134. data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
  135. data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
  136. data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
  137. data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
  138. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
  139. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  140. data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  141. data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
  142. data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
  143. data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
  144. data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
  145. data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
  146. data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
  147. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +84 -0
  148. data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
  149. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  150. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  151. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  152. data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
  153. data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
  154. data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
  155. data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
  156. data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
  157. data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
  158. data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
  159. data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
  160. data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
  161. data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
  162. data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
  163. data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
  164. data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
  165. data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
  166. data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
  167. data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
  168. data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
  169. data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
  170. data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
  171. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
  172. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  173. data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
  174. data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  175. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  176. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
  177. data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
  178. data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
  179. data/lib/mocktail/sorbet/mocktail.rb +154 -0
  180. data/lib/mocktail/sorbet.rb +1 -0
  181. data/lib/mocktail/stringifies_method_signature.rb +2 -0
  182. data/lib/mocktail/typed.rb +3 -0
  183. data/lib/mocktail/value/cabinet.rb +8 -1
  184. data/lib/mocktail/value/call.rb +44 -12
  185. data/lib/mocktail/value/demo_config.rb +6 -7
  186. data/lib/mocktail/value/double.rb +6 -7
  187. data/lib/mocktail/value/double_data.rb +11 -7
  188. data/lib/mocktail/value/explanation.rb +28 -3
  189. data/lib/mocktail/value/explanation_data.rb +14 -0
  190. data/lib/mocktail/value/fake_method_data.rb +7 -6
  191. data/lib/mocktail/value/matcher_registry.rb +2 -0
  192. data/lib/mocktail/value/no_explanation_data.rb +16 -0
  193. data/lib/mocktail/value/signature.rb +19 -27
  194. data/lib/mocktail/value/stubbing.rb +11 -12
  195. data/lib/mocktail/value/top_shelf.rb +5 -0
  196. data/lib/mocktail/value/type_replacement.rb +7 -8
  197. data/lib/mocktail/value/type_replacement_data.rb +10 -7
  198. data/lib/mocktail/value/unsatisfying_call.rb +5 -6
  199. data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
  200. data/lib/mocktail/value.rb +5 -2
  201. data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
  202. data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
  203. data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
  204. data/lib/mocktail/verifies_call.rb +3 -0
  205. data/lib/mocktail/version.rb +8 -1
  206. data/lib/mocktail.rb +46 -5
  207. data/mocktail.gemspec +8 -4
  208. data/rbi/mocktail-pregenerated.rbi +1923 -0
  209. data/rbi/mocktail.rbi +77 -0
  210. data/rbi/sorbet-runtime.rbi +29 -0
  211. data/script/build +47 -0
  212. data/script/setup +17 -0
  213. data/script/spoom_me +7 -0
  214. data/script/strip_sigils +17 -0
  215. data/script/test +33 -0
  216. data/script/test_double_require_warnings +46 -0
  217. data/script/update +21 -0
  218. data/sorbet/config +12 -0
  219. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  220. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  221. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  222. data/sorbet/rbi/gems/docile@1.4.0.rbi +376 -0
  223. data/sorbet/rbi/gems/json@2.6.3.rbi +1533 -0
  224. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  225. data/sorbet/rbi/gems/lint_roller@1.0.0.rbi +230 -0
  226. data/sorbet/rbi/gems/m@1.6.1.rbi +257 -0
  227. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  228. data/sorbet/rbi/gems/minitest@5.18.0.rbi +1491 -0
  229. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  230. data/sorbet/rbi/gems/parallel@1.23.0.rbi +273 -0
  231. data/sorbet/rbi/gems/parser@3.2.2.1.rbi +7253 -0
  232. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  233. data/sorbet/rbi/gems/rake@13.0.6.rbi +3024 -0
  234. data/sorbet/rbi/gems/rbi@0.0.16.rbi +3049 -0
  235. data/sorbet/rbi/gems/regexp_parser@2.8.0.rbi +3748 -0
  236. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
  237. data/sorbet/rbi/gems/rubocop-ast@1.29.0.rbi +7003 -0
  238. data/sorbet/rbi/gems/rubocop-performance@1.18.0.rbi +8 -0
  239. data/sorbet/rbi/gems/rubocop-sorbet@0.7.0.rbi +1043 -0
  240. data/sorbet/rbi/gems/rubocop@1.52.0.rbi +55500 -0
  241. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  242. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +216 -0
  243. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2148 -0
  244. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +8 -0
  245. data/sorbet/rbi/gems/spoom@1.2.1.rbi +2536 -0
  246. data/sorbet/rbi/gems/standard-custom@1.0.1.rbi +63 -0
  247. data/sorbet/rbi/gems/standard-performance@1.1.0.rbi +8 -0
  248. data/sorbet/rbi/gems/standard@1.29.0.rbi +850 -0
  249. data/sorbet/rbi/gems/tapioca@0.11.6.rbi +3301 -0
  250. data/sorbet/rbi/gems/thor@1.2.2.rbi +3965 -0
  251. data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
  252. data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
  253. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  254. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  255. data/sorbet/tapioca/config.yml +13 -0
  256. data/sorbet/tapioca/require.rb +4 -0
  257. data/spoom_data/05f0c49.json +1 -0
  258. data/spoom_data/1657012.json +1 -0
  259. data/spoom_data/167434b.json +1 -0
  260. data/spoom_data/1d0ba5b.json +1 -0
  261. data/spoom_data/1ffa724.json +1 -0
  262. data/spoom_data/2129b3d.json +1 -0
  263. data/spoom_data/24b1c92.json +1 -0
  264. data/spoom_data/2c46aee.json +1 -0
  265. data/spoom_data/305ec0b.json +1 -0
  266. data/spoom_data/30e9528.json +1 -0
  267. data/spoom_data/4638cd5.json +1 -0
  268. data/spoom_data/47c7dad.json +1 -0
  269. data/spoom_data/4b1edef.json +1 -0
  270. data/spoom_data/4de157f.json +1 -0
  271. data/spoom_data/526e7db.json +1 -0
  272. data/spoom_data/5d093b9.json +1 -0
  273. data/spoom_data/5db3b43.json +1 -0
  274. data/spoom_data/5fe2a65.json +1 -0
  275. data/spoom_data/6891312.json +1 -0
  276. data/spoom_data/6b0fef4.json +1 -0
  277. data/spoom_data/6b83d12.json +1 -0
  278. data/spoom_data/74c83c2.json +1 -0
  279. data/spoom_data/7644ff4.json +1 -0
  280. data/spoom_data/79054db.json +1 -0
  281. data/spoom_data/814e515.json +1 -0
  282. data/spoom_data/88c3b60.json +1 -0
  283. data/spoom_data/8bd4b6e.json +1 -0
  284. data/spoom_data/93f8153.json +1 -0
  285. data/spoom_data/95242fe.json +1 -0
  286. data/spoom_data/97f4c09.json +1 -0
  287. data/spoom_data/a13d150.json +1 -0
  288. data/spoom_data/a17f215.json +1 -0
  289. data/spoom_data/b705a9d.json +1 -0
  290. data/spoom_data/ba19195.json +1 -0
  291. data/spoom_data/bef51ca.json +1 -0
  292. data/spoom_data/dbb595a.json +1 -0
  293. data/spoom_data/e8fab92.json +1 -0
  294. data/spoom_data/f166c87.json +1 -0
  295. data/spoom_data/f57992d.json +1 -0
  296. data/spoom_data/f5a1e40.json +1 -0
  297. data/spoom_data/f6d6431.json +1 -0
  298. data/spoom_data/f72b67c.json +1 -0
  299. data/spoom_data/fc2f231.json +1 -0
  300. data/spoom_report.html +1248 -0
  301. data/src/mocktail/collects_calls.rb +18 -0
  302. data/src/mocktail/debug.rb +55 -0
  303. data/src/mocktail/dsl.rb +46 -0
  304. data/src/mocktail/errors.rb +19 -0
  305. data/src/mocktail/explains_nils.rb +41 -0
  306. data/src/mocktail/explains_thing.rb +137 -0
  307. data/src/mocktail/grabs_original_method_parameters.rb +33 -0
  308. data/src/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  309. data/src/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  310. data/src/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  311. data/src/mocktail/handles_dry_call/logs_call.rb +12 -0
  312. data/src/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  313. data/src/mocktail/handles_dry_call.rb +25 -0
  314. data/src/mocktail/handles_dry_new_call.rb +42 -0
  315. data/src/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  316. data/src/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  317. data/src/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  318. data/src/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  319. data/src/mocktail/imitates_type/makes_double.rb +29 -0
  320. data/src/mocktail/imitates_type.rb +29 -0
  321. data/src/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  322. data/src/mocktail/initializes_mocktail.rb +25 -0
  323. data/src/mocktail/matcher_presentation.rb +21 -0
  324. data/src/mocktail/matchers/any.rb +27 -0
  325. data/src/mocktail/matchers/base.rb +39 -0
  326. data/src/mocktail/matchers/captor.rb +76 -0
  327. data/src/mocktail/matchers/includes.rb +32 -0
  328. data/src/mocktail/matchers/includes_hash.rb +12 -0
  329. data/src/mocktail/matchers/includes_key.rb +12 -0
  330. data/src/mocktail/matchers/includes_string.rb +12 -0
  331. data/src/mocktail/matchers/is_a.rb +17 -0
  332. data/src/mocktail/matchers/matches.rb +19 -0
  333. data/src/mocktail/matchers/not.rb +17 -0
  334. data/src/mocktail/matchers/numeric.rb +27 -0
  335. data/src/mocktail/matchers/that.rb +32 -0
  336. data/src/mocktail/matchers.rb +19 -0
  337. data/src/mocktail/raises_neato_no_method_error.rb +93 -0
  338. data/src/mocktail/records_demonstration.rb +43 -0
  339. data/src/mocktail/registers_matcher.rb +65 -0
  340. data/src/mocktail/registers_stubbing.rb +31 -0
  341. data/src/mocktail/replaces_next.rb +55 -0
  342. data/src/mocktail/replaces_type/redefines_new.rb +32 -0
  343. data/src/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  344. data/src/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  345. data/src/mocktail/replaces_type.rb +36 -0
  346. data/src/mocktail/resets_state.rb +14 -0
  347. data/src/mocktail/share/bind.rb +18 -0
  348. data/src/mocktail/share/cleans_backtrace.rb +22 -0
  349. data/src/mocktail/share/creates_identifier.rb +39 -0
  350. data/src/mocktail/share/determines_matching_calls.rb +72 -0
  351. data/src/mocktail/share/stringifies_call.rb +84 -0
  352. data/src/mocktail/share/stringifies_method_name.rb +16 -0
  353. data/src/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  354. data/src/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  355. data/src/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  356. data/src/mocktail/simulates_argument_error.rb +36 -0
  357. data/src/mocktail/sorbet.rb +3 -0
  358. data/src/mocktail/stringifies_method_signature.rb +53 -0
  359. data/src/mocktail/typed.rb +5 -0
  360. data/src/mocktail/value/cabinet.rb +91 -0
  361. data/src/mocktail/value/call.rb +51 -0
  362. data/src/mocktail/value/demo_config.rb +10 -0
  363. data/src/mocktail/value/double.rb +10 -0
  364. data/src/mocktail/value/double_data.rb +15 -0
  365. data/src/mocktail/value/explanation.rb +68 -0
  366. data/src/mocktail/value/explanation_data.rb +19 -0
  367. data/src/mocktail/value/fake_method_data.rb +11 -0
  368. data/src/mocktail/value/matcher_registry.rb +27 -0
  369. data/src/mocktail/value/no_explanation_data.rb +20 -0
  370. data/src/mocktail/value/signature.rb +35 -0
  371. data/src/mocktail/value/stubbing.rb +26 -0
  372. data/src/mocktail/value/top_shelf.rb +79 -0
  373. data/src/mocktail/value/type_replacement.rb +11 -0
  374. data/src/mocktail/value/type_replacement_data.rb +19 -0
  375. data/src/mocktail/value/unsatisfying_call.rb +9 -0
  376. data/src/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  377. data/src/mocktail/value.rb +19 -0
  378. data/src/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  379. data/src/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  380. data/src/mocktail/verifies_call/raises_verification_error.rb +74 -0
  381. data/src/mocktail/verifies_call.rb +37 -0
  382. data/src/mocktail/version.rb +12 -0
  383. data/src/mocktail.rb +154 -0
  384. data/sub_projects/rbi_generator/Gemfile +4 -0
  385. data/sub_projects/rbi_generator/Gemfile.lock +63 -0
  386. data/sub_projects/rbi_generator/sorbet/rbi/gems/.gitattributes +1 -0
  387. data/sub_projects/sorbet_user/Gemfile +13 -0
  388. data/sub_projects/sorbet_user/Gemfile.lock +126 -0
  389. data/sub_projects/sorbet_user/Rakefile +10 -0
  390. data/sub_projects/sorbet_user/bin/tapioca +27 -0
  391. data/sub_projects/sorbet_user/rbi/mocktail.rbi +9 -0
  392. data/sub_projects/sorbet_user/script/test +13 -0
  393. data/sub_projects/sorbet_user/sorbet/config +4 -0
  394. data/sub_projects/sorbet_user/sorbet/rbi/gems/.gitattributes +1 -0
  395. data/sub_projects/sorbet_user/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
  396. data/sub_projects/sorbet_user/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  397. data/sub_projects/sorbet_user/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  398. data/sub_projects/sorbet_user/sorbet/rbi/gems/json@2.21.2.rbi +2140 -0
  399. data/sub_projects/sorbet_user/sorbet/rbi/gems/language_server-protocol@3.17.0.4.rbi +14244 -0
  400. data/sub_projects/sorbet_user/sorbet/rbi/gems/lint_roller@1.1.0.rbi +239 -0
  401. data/sub_projects/sorbet_user/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
  402. data/sub_projects/sorbet_user/sorbet/rbi/gems/m@1.6.2.rbi +258 -0
  403. data/sub_projects/sorbet_user/sorbet/rbi/gems/method_source@1.1.0.rbi +9 -0
  404. data/sub_projects/sorbet_user/sorbet/rbi/gems/minitest@5.27.0.rbi +1549 -0
  405. data/sub_projects/sorbet_user/sorbet/rbi/gems/mocktail@3.0.0.rbi +1923 -0
  406. data/sub_projects/sorbet_user/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  407. data/sub_projects/sorbet_user/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  408. data/sub_projects/sorbet_user/sorbet/rbi/gems/parser@3.3.7.4.rbi +5451 -0
  409. data/sub_projects/sorbet_user/sorbet/rbi/gems/prism@1.4.0.rbi +41732 -0
  410. data/sub_projects/sorbet_user/sorbet/rbi/gems/racc@1.8.1.rbi +164 -0
  411. data/sub_projects/sorbet_user/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  412. data/sub_projects/sorbet_user/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
  413. data/sub_projects/sorbet_user/sorbet/rbi/gems/rbi@0.3.1.rbi +6599 -0
  414. data/sub_projects/sorbet_user/sorbet/rbi/gems/rbs@3.9.2.rbi +6976 -0
  415. data/sub_projects/sorbet_user/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3795 -0
  416. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-ast@1.43.0.rbi +7717 -0
  417. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-performance@1.24.0.rbi +9 -0
  418. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-sorbet@0.9.0.rbi +9 -0
  419. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop@1.73.2.rbi +61306 -0
  420. data/sub_projects/sorbet_user/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  421. data/sub_projects/sorbet_user/sorbet/rbi/gems/sorbet-eraser@0.3.1.rbi +8 -0
  422. data/sub_projects/sorbet_user/sorbet/rbi/gems/spoom@1.6.1.rbi +7274 -0
  423. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-custom@1.0.2.rbi +8 -0
  424. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-performance@1.7.0.rbi +9 -0
  425. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-sorbet@0.0.3.rbi +53 -0
  426. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard@1.47.0.rbi +935 -0
  427. data/sub_projects/sorbet_user/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
  428. data/sub_projects/sorbet_user/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  429. data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
  430. data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +254 -0
  431. data/sub_projects/sorbet_user/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  432. data/sub_projects/sorbet_user/sorbet/rbi/gems/yard@0.9.45.rbi +18916 -0
  433. data/sub_projects/sorbet_user/sorbet/tapioca/config.yml +13 -0
  434. data/sub_projects/sorbet_user/sorbet/tapioca/require.rb +4 -0
  435. data/sub_projects/sorbet_user/test/ensure_type_safety_test.rb +21 -0
  436. data/sub_projects/sorbet_user/test/paint_by_number_test.rb +113 -0
  437. data/sub_projects/sorbet_user/test/sorbet_test.rb +289 -0
  438. data/sub_projects/sorbet_user/test/test_helper.rb +35 -0
  439. data/sub_projects/untyped_user/Gemfile +4 -0
  440. data/sub_projects/untyped_user/Gemfile.lock +25 -0
  441. data/sub_projects/untyped_user/antitype_test.rb +54 -0
  442. data/sub_projects/untyped_user/script/test +5 -0
  443. metadata +392 -9
@@ -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
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ # Captors are conceptually complex implementations, but with a simple usage/purpose:
5
+ # They are values the user can create and hold onto that will return a matcher
6
+ # and then "capture" the value made by the real call, for later analysis & assertion.
7
+ #
8
+ # Unlike other matchers, these don't make any useful sense for stubbing, but are
9
+ # very useful when asserting complication call verifications
10
+ #
11
+ # The fact the user will need the reference outside the verification call is
12
+ # why this is a top-level method on Mocktail, and not included in the |m| block
13
+ # arg to stubs/verify
14
+ #
15
+ # See Mockito, which is the earliest implementation I know of:
16
+ # https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Captor.html
17
+ class Captor
18
+ extend T::Sig
19
+
20
+ class Capture < Mocktail::Matchers::Base
21
+ extend T::Sig
22
+
23
+ sig { returns(Symbol) }
24
+ def self.matcher_name
25
+ :capture
26
+ end
27
+
28
+ sig { returns(T.untyped) }
29
+ attr_reader :value
30
+
31
+ sig { void }
32
+ def initialize
33
+ @value = T.let(nil, T.untyped)
34
+ @captured = T.let(false, T::Boolean)
35
+ end
36
+
37
+ sig { params(actual: T.untyped).returns(TrueClass) }
38
+ def match?(actual)
39
+ @value = actual
40
+ @captured = true
41
+ true
42
+ end
43
+
44
+ sig { returns(T::Boolean) }
45
+ def captured?
46
+ @captured
47
+ end
48
+
49
+ sig { returns(String) }
50
+ def inspect
51
+ "capture"
52
+ end
53
+ end
54
+
55
+ # This T.untyped is intentional. Even though a Capture is surely returned,
56
+ # in order for a verification demonstration to pass its own type check,
57
+ # it needs to think it's being returned whatever parameter is expected
58
+ sig { returns(T.untyped) }
59
+ attr_reader :capture
60
+
61
+ sig { void }
62
+ def initialize
63
+ @capture = T.let(Capture.new, Capture)
64
+ end
65
+
66
+ sig { returns(T::Boolean) }
67
+ def captured?
68
+ @capture.captured?
69
+ end
70
+
71
+ sig { returns(T.untyped) }
72
+ def value
73
+ @capture.value
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class Includes < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :includes
10
+ end
11
+
12
+ sig { params(expecteds: T.untyped).void }
13
+ def initialize(*expecteds)
14
+ @expecteds = T.let(expecteds, T::Array[T.untyped])
15
+ end
16
+
17
+ sig { params(actual: T.untyped).returns(T::Boolean) }
18
+ def match?(actual)
19
+ @expecteds.all? { |expected|
20
+ (actual.respond_to?(:include?) && actual.include?(expected)) ||
21
+ (actual.is_a?(Hash) && expected.is_a?(Hash) && expected.all? { |k, v| actual[k] == v })
22
+ }
23
+ rescue
24
+ false
25
+ end
26
+
27
+ sig { returns(String) }
28
+ def inspect
29
+ "#{self.class.matcher_name}(#{@expecteds.map(&:inspect).join(", ")})"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class IncludesHash < Includes
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :includes_hash
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class IncludesKey < Includes
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :includes_key
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class IncludesString < Includes
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :includes_string
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class IsA < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :is_a
10
+ end
11
+
12
+ sig { params(actual: T.untyped).returns(T::Boolean) }
13
+ def match?(actual)
14
+ actual.is_a?(@expected)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class Matches < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :matches
10
+ end
11
+
12
+ sig { params(actual: T.untyped).returns(T::Boolean) }
13
+ def match?(actual)
14
+ actual.respond_to?(:match?) && actual.match?(@expected)
15
+ rescue
16
+ false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class Not < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :not
10
+ end
11
+
12
+ sig { params(actual: T.untyped).returns(T::Boolean) }
13
+ def match?(actual)
14
+ @expected != actual
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class Numeric < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :numeric
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.untyped).returns(T::Boolean) }
18
+ def match?(actual)
19
+ actual.is_a?(::Numeric)
20
+ end
21
+
22
+ sig { returns(String) }
23
+ def inspect
24
+ "numeric"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+
3
+ module Mocktail::Matchers
4
+ class That < Base
5
+ extend T::Sig
6
+
7
+ sig { returns(Symbol) }
8
+ def self.matcher_name
9
+ :that
10
+ end
11
+
12
+ sig { params(blk: T.nilable(T.proc.params(actual: T.untyped).returns(T.untyped))).void }
13
+ def initialize(&blk)
14
+ if blk.nil?
15
+ raise ArgumentError.new("The `that` matcher must be passed a block (e.g. `that { |arg| … }`)")
16
+ end
17
+ @blk = T.let(blk, T.proc.params(actual: T.untyped).returns(T.untyped))
18
+ end
19
+
20
+ sig { params(actual: T.untyped).returns(T::Boolean) }
21
+ def match?(actual)
22
+ @blk.call(actual)
23
+ rescue
24
+ false
25
+ end
26
+
27
+ sig { returns(String) }
28
+ def inspect
29
+ "that {…}"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ module Matchers
5
+ end
6
+ end
7
+
8
+ require_relative "matchers/base"
9
+ require_relative "matchers/any"
10
+ require_relative "matchers/captor"
11
+ require_relative "matchers/includes"
12
+ require_relative "matchers/includes_string"
13
+ require_relative "matchers/includes_hash"
14
+ require_relative "matchers/includes_key"
15
+ require_relative "matchers/is_a"
16
+ require_relative "matchers/matches"
17
+ require_relative "matchers/not"
18
+ require_relative "matchers/numeric"
19
+ require_relative "matchers/that"