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
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class ResetsState
3
+ extend T::Sig
4
+
3
5
  def reset
4
6
  TopShelf.instance.reset_current_thread!
5
7
  Mocktail.cabinet.reset!
@@ -1,13 +1,15 @@
1
1
  module Mocktail
2
2
  module Bind
3
- def self.call(mock, method_name, *args, **kwargs, &blk)
3
+ # sig intentionally omitted, because the wrapper will cause infinite recursion if certain methods are mocked
4
+ def self.call(mock, method_name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
4
5
  if Mocktail.cabinet.double_for_instance(mock)
5
- Object.instance_method(method_name).bind_call(mock, *args, **kwargs, &blk)
6
- elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(mock)) &&
6
+ Object.instance_method(method_name).bind_call(mock, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
7
+ elsif (mock.is_a?(Module) || mock.is_a?(Class)) &&
8
+ (type_replacement = TopShelf.instance.type_replacement_if_exists_for(mock)) &&
7
9
  (og_method = type_replacement.original_methods&.find { |m| m.name == method_name })
8
- og_method.call(*args, **kwargs, &blk)
10
+ og_method.call(*args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
9
11
  else
10
- mock.__send__(method_name, *args, **kwargs, &blk)
12
+ mock.__send__(method_name, *args, **kwargs, &blk) # standard:disable Style/ArgumentsForwarding
11
13
  end
12
14
  end
13
15
  end
@@ -1,15 +1,13 @@
1
- require "pathname"
2
-
3
1
  module Mocktail
4
2
  class CleansBacktrace
5
- BASE_PATH = (Pathname.new(__FILE__) + "../../..").to_s
3
+ extend T::Sig
6
4
 
7
5
  def clean(error)
8
6
  raise error
9
- rescue => e
7
+ rescue error.class => e
10
8
  e.tap do |e|
11
9
  e.set_backtrace(e.backtrace.drop_while { |frame|
12
- frame.start_with?(BASE_PATH)
10
+ frame.start_with?(BASE_PATH, BASE_PATH) || frame.match?(/[\\|\/]sorbet-runtime.*[\\|\/]lib[\\|\/]types[\\|\/]private/)
13
11
  })
14
12
  end
15
13
  end
@@ -1,19 +1,26 @@
1
1
  module Mocktail
2
2
  class CreatesIdentifier
3
+ extend T::Sig
4
+
3
5
  KEYWORDS = %w[__FILE__ __LINE__ alias and begin BEGIN break case class def defined? do else elsif end END ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield]
4
6
 
5
7
  def create(s, default: "identifier", max_length: 24)
6
- id = s.to_s.downcase
7
- .gsub(/:0x[0-9a-f]+/, "") # Lazy attempt to wipe any Object:0x802beef identifiers
8
- .gsub(/[^\w\s]/, "")
9
- .gsub(/^\d+/, "")[0...max_length]
10
- .strip
11
- .gsub(/\s+/, "_") # snake_case
8
+ case s
9
+ when Kernel
10
+ id = (s.to_s.downcase
11
+ .gsub(/:0x[0-9a-f]+/, "") # Lazy attempt to wipe any Object:0x802beef identifiers
12
+ .gsub(/[^\w\s]/, "")
13
+ .gsub(/^\d+/, "")[0...max_length] || "")
14
+ .strip
15
+ .gsub(/\s+/, "_") # snake_case
12
16
 
13
- if id.empty?
14
- default
17
+ if id.empty?
18
+ default
19
+ else
20
+ unreserved(id, default)
21
+ end
15
22
  else
16
- unreserved(id, default)
23
+ default
17
24
  end
18
25
  end
19
26
 
@@ -2,6 +2,8 @@ require_relative "bind"
2
2
 
3
3
  module Mocktail
4
4
  class DeterminesMatchingCalls
5
+ extend T::Sig
6
+
5
7
  def determine(real_call, demo_call, demo_config)
6
8
  Bind.call(real_call.double, :==, demo_call.double) &&
7
9
  real_call.method == demo_call.method &&
@@ -40,7 +42,7 @@ module Mocktail
40
42
  end
41
43
 
42
44
  def blocks_match?(real_block, demo_block, ignore_block)
43
- ignore_block ||
45
+ !!(ignore_block ||
44
46
  (real_block.nil? && demo_block.nil?) ||
45
47
  (
46
48
  real_block && demo_block &&
@@ -48,7 +50,7 @@ module Mocktail
48
50
  demo_block == real_block ||
49
51
  demo_block.call(real_block)
50
52
  )
51
- )
53
+ ))
52
54
  end
53
55
 
54
56
  def match?(real_arg, demo_arg)
@@ -1,12 +1,13 @@
1
1
  module Mocktail
2
2
  class StringifiesCall
3
+ extend T::Sig
4
+
3
5
  def stringify(call, anonymous_blocks: false, always_parens: false)
4
6
  "#{call.method}#{args_to_s(call, parens: always_parens)}#{blockify(call.block, anonymous: anonymous_blocks)}"
5
7
  end
6
8
 
7
9
  def stringify_multiple(calls, nonzero_message:, zero_message:,
8
10
  anonymous_blocks: false, always_parens: false)
9
-
10
11
  if calls.empty?
11
12
  "#{zero_message}.\n"
12
13
  else
@@ -31,16 +32,18 @@ module Mocktail
31
32
  "(#{args_lists.join(", ")})"
32
33
  elsif parens
33
34
  "()"
35
+ else
36
+ ""
34
37
  end
35
38
  end
36
39
 
37
40
  def argify(args)
38
- return unless args && !args.empty?
41
+ return unless !args.empty?
39
42
  args.map(&:inspect).join(", ")
40
43
  end
41
44
 
42
45
  def kwargify(kwargs)
43
- return unless kwargs && !kwargs.empty?
46
+ return unless !kwargs.empty?
44
47
  kwargs.map { |key, val| "#{key}: #{val.inspect}" }.join(", ")
45
48
  end
46
49
 
@@ -1,8 +1,10 @@
1
1
  module Mocktail
2
2
  class StringifiesMethodName
3
+ extend T::Sig
4
+
3
5
  def stringify(call)
4
6
  [
5
- call.original_type.name,
7
+ call.original_type&.name,
6
8
  call.singleton ? "." : "#",
7
9
  call.method
8
10
  ].join
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class ReconcilesArgsWithParams
3
+ extend T::Sig
4
+
3
5
  def reconcile(signature)
4
6
  args_match?(signature.positional_params, signature.positional_args) &&
5
7
  kwargs_match?(signature.keyword_params, signature.keyword_args)
@@ -1,5 +1,7 @@
1
1
  module Mocktail
2
2
  class RecreatesMessage
3
+ extend T::Sig
4
+
3
5
  def recreate(signature)
4
6
  req_args = signature.positional_params.required.size
5
7
  allowed_args = signature.positional_params.allowed.size
@@ -2,26 +2,33 @@ require_relative "../share/bind"
2
2
 
3
3
  module Mocktail
4
4
  class TransformsParams
5
- def transform(dry_call, params: dry_call.original_method.parameters)
5
+ extend T::Sig
6
+
7
+ def initialize
8
+ @grabs_original_method_parameters = GrabsOriginalMethodParameters.new
9
+ end
10
+
11
+ def transform(dry_call, params: nil)
12
+ params ||= @grabs_original_method_parameters.grab(dry_call.original_method)
6
13
  params = name_unnamed_params(params)
7
14
 
8
15
  Signature.new(
9
16
  positional_params: Params.new(
10
17
  all: params.select { |t, _|
11
18
  [:req, :opt, :rest].any? { |param_type| Bind.call(t, :==, param_type) }
12
- }.map { |_, name| name },
13
- required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |_, n| n },
14
- optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |_, n| n },
19
+ }.map { |pair| pair.fetch(1) },
20
+ required: params.select { |t, _| Bind.call(t, :==, :req) }.map { |pair| pair.fetch(1) },
21
+ optional: params.select { |t, _| Bind.call(t, :==, :opt) }.map { |pair| pair.fetch(1) },
15
22
  rest: params.find { |t, _| Bind.call(t, :==, :rest) }&.last
16
23
  ),
17
24
  positional_args: dry_call.args,
18
25
 
19
26
  keyword_params: Params.new(
20
- all: params.select { |type, _|
27
+ all: params.select { |type, _| # standard:disable Style/HashSlice
21
28
  [:keyreq, :key, :keyrest].include?(type)
22
- }.map { |_, name| name },
23
- required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |_, n| n },
24
- optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |_, n| n },
29
+ }.map { |pair| pair.fetch(1) },
30
+ required: params.select { |t, _| Bind.call(t, :==, :keyreq) }.map { |pair| pair.fetch(1) },
31
+ optional: params.select { |t, _| Bind.call(t, :==, :key) }.map { |pair| pair.fetch(1) },
25
32
  rest: params.find { |t, _| Bind.call(t, :==, :keyrest) }&.last
26
33
  ),
27
34
  keyword_args: dry_call.kwargs,
@@ -36,7 +43,7 @@ module Mocktail
36
43
  def name_unnamed_params(params)
37
44
  params.map.with_index { |param, i|
38
45
  if param.size == 1
39
- param + ["unnamed_arg_#{i + 1}"]
46
+ param + [:"unnamed_arg_#{i + 1}"]
40
47
  else
41
48
  param
42
49
  end
@@ -6,6 +6,8 @@ require_relative "share/stringifies_call"
6
6
 
7
7
  module Mocktail
8
8
  class SimulatesArgumentError
9
+ extend T::Sig
10
+
9
11
  def initialize
10
12
  @transforms_params = TransformsParams.new
11
13
  @reconciles_args_with_params = ReconcilesArgsWithParams.new
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class CollectsCalls
5
+ extend T::Sig
6
+
7
+ sig { params(double: Object, method_name: T.nilable(Symbol)).returns(T::Array[Call]) }
8
+ def collect(double, method_name)
9
+ calls = ExplainsThing.new.explain(double).reference.calls
10
+
11
+ if method_name.nil?
12
+ calls
13
+ else
14
+ calls.select { |call| call.method.to_s == method_name.to_s }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ module Debug
5
+ extend T::Sig
6
+
7
+ # It would be easy and bad for the mocktail lib to call something like
8
+ #
9
+ # double == other_double
10
+ #
11
+ # But if it's a double, that means anyone who stubs that method could change
12
+ # the internal behavior of the library in unexpected ways (as happened here:
13
+ # https://github.com/testdouble/mocktail/issues/7 )
14
+ #
15
+ # For that reason when we run our tests, we also want to blow up if this
16
+ # happens unintentionally. This works in conjunction with the test
17
+ # MockingMethodfulClassesTest, because it mocks every defined method on the
18
+ # mocked BasicObject
19
+ sig { void }
20
+ def self.guard_against_mocktail_accidentally_calling_mocks_if_debugging!
21
+ return unless ENV["MOCKTAIL_DEBUG_ACCIDENTAL_INTERNAL_MOCK_CALLS"]
22
+ raise Mocktail::Error
23
+ rescue Mocktail::Error => e
24
+ base_path = Pathname.new(__FILE__).dirname.to_s
25
+ backtrace_minus_this_and_whoever_called_this = e.backtrace&.[](2..)
26
+ internal_call_sites = backtrace_minus_this_and_whoever_called_this&.take_while { |call_site|
27
+ # the "in `block"/"in 'block" (Ruby 3.4+ uses a single quote instead of a
28
+ # backtick) is very confusing but necessary to include lines after
29
+ # a stubs { blah.foo }.with { … } call, since that's when most of the
30
+ # good stuff happens
31
+ call_site.start_with?(base_path) || call_site.match?(/in [`']block/)
32
+ }&.reject { |call_site| call_site.match?(/in [`']block/) } || []
33
+
34
+ approved_call_sites = [
35
+ /fulfills_stubbing.rb:(16|20)/,
36
+ /validates_arguments.rb:(18|23)/,
37
+ /validates_arguments.rb:(21|26)/
38
+ ]
39
+ if internal_call_sites.any? && approved_call_sites.none? { |approved_call_site|
40
+ internal_call_sites.first&.match?(approved_call_site)
41
+ }
42
+ raise Error.new <<~MSG
43
+ Unauthorized internal call of a mock internally by Mocktail itself:
44
+
45
+ #{internal_call_sites.first}
46
+
47
+ Offending call's complete stack trace:
48
+
49
+ #{backtrace_minus_this_and_whoever_called_this&.join("\n")}
50
+ ==END OFFENDING TRACE==
51
+ MSG
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,46 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ module DSL
5
+ extend T::Sig
6
+
7
+ sig {
8
+ type_parameters(:T)
9
+ .params(
10
+ ignore_block: T::Boolean,
11
+ ignore_extra_args: T::Boolean,
12
+ ignore_arity: T::Boolean,
13
+ times: T.nilable(Integer),
14
+ demo: T.proc.params(matchers: Mocktail::MatcherPresentation).returns(T.type_parameter(:T))
15
+ )
16
+ .returns(Mocktail::Stubbing[T.type_parameter(:T)])
17
+ }
18
+ def stubs(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
19
+ RegistersStubbing.new.register(demo, DemoConfig.new(
20
+ ignore_block: ignore_block,
21
+ ignore_extra_args: ignore_extra_args,
22
+ ignore_arity: ignore_arity,
23
+ times: times
24
+ ))
25
+ end
26
+
27
+ sig {
28
+ type_parameters(:T)
29
+ .params(
30
+ ignore_block: T::Boolean,
31
+ ignore_extra_args: T::Boolean,
32
+ ignore_arity: T::Boolean,
33
+ times: T.nilable(Integer),
34
+ demo: T.proc.params(matchers: Mocktail::MatcherPresentation).void
35
+ ).void
36
+ }
37
+ def verify(ignore_block: false, ignore_extra_args: false, ignore_arity: false, times: nil, &demo)
38
+ VerifiesCall.new.verify(demo, DemoConfig.new(
39
+ ignore_block: ignore_block,
40
+ ignore_extra_args: ignore_extra_args,
41
+ ignore_arity: ignore_arity,
42
+ times: times
43
+ ))
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class Error < StandardError; end
5
+
6
+ class UnexpectedError < Error; end
7
+
8
+ class UnsupportedMocktail < Error; end
9
+
10
+ class MissingDemonstrationError < Error; end
11
+
12
+ class AmbiguousDemonstrationError < Error; end
13
+
14
+ class InvalidMatcherError < Error; end
15
+
16
+ class VerificationError < Error; end
17
+
18
+ class TypeCheckingError < Error; end
19
+ end
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+
3
+ require_relative "share/stringifies_method_name"
4
+ require_relative "share/stringifies_call"
5
+
6
+ module Mocktail
7
+ class ExplainsNils
8
+ extend T::Sig
9
+
10
+ sig { void }
11
+ def initialize
12
+ @stringifies_method_name = T.let(StringifiesMethodName.new, StringifiesMethodName)
13
+ @stringifies_call = T.let(StringifiesCall.new, StringifiesCall)
14
+ end
15
+
16
+ sig { returns(T::Array[UnsatisfyingCallExplanation]) }
17
+ def explain
18
+ Mocktail.cabinet.unsatisfying_calls.map { |unsatisfying_call|
19
+ dry_call = unsatisfying_call.call
20
+ other_stubbings = unsatisfying_call.other_stubbings
21
+
22
+ UnsatisfyingCallExplanation.new(unsatisfying_call, <<~MSG)
23
+ `nil' was returned by a mocked `#{@stringifies_method_name.stringify(dry_call)}' method
24
+ because none of its configured stubbings were satisfied.
25
+
26
+ The actual call:
27
+
28
+ #{@stringifies_call.stringify(dry_call, always_parens: true)}
29
+
30
+ The call site:
31
+
32
+ #{unsatisfying_call.backtrace.first}
33
+
34
+ #{@stringifies_call.stringify_multiple(other_stubbings.map(&:recording),
35
+ nonzero_message: "Stubbings configured prior to this call but not satisfied by it",
36
+ zero_message: "No stubbings were configured on this method")}
37
+ MSG
38
+ }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,137 @@
1
+ # typed: strict
2
+
3
+ require_relative "share/stringifies_method_name"
4
+ require_relative "share/stringifies_call"
5
+
6
+ module Mocktail
7
+ class ExplainsThing
8
+ extend T::Sig
9
+
10
+ sig { void }
11
+ def initialize
12
+ @stringifies_method_name = T.let(StringifiesMethodName.new, StringifiesMethodName)
13
+ @stringifies_call = T.let(StringifiesCall.new, StringifiesCall)
14
+ end
15
+
16
+ sig { params(thing: Object).returns(Explanation) }
17
+ def explain(thing)
18
+ if (double = Mocktail.cabinet.double_for_instance(thing))
19
+ double_explanation(double)
20
+ elsif (thing.is_a?(Module) || thing.is_a?(Class)) &&
21
+ (type_replacement = TopShelf.instance.type_replacement_if_exists_for(thing))
22
+ replaced_type_explanation(type_replacement)
23
+ elsif (fake_method_explanation = fake_method_explanation_for(thing))
24
+ fake_method_explanation
25
+ else
26
+ no_explanation(thing)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ sig { params(thing: Object).returns(T.nilable(FakeMethodExplanation)) }
33
+ def fake_method_explanation_for(thing)
34
+ return unless thing.is_a?(Method)
35
+ method = thing
36
+ receiver = thing.receiver
37
+
38
+ receiver_data = if (double = Mocktail.cabinet.double_for_instance(receiver))
39
+ data_for_double(double)
40
+ elsif (type_replacement = TopShelf.instance.type_replacement_if_exists_for(receiver))
41
+ data_for_type_replacement(type_replacement)
42
+ end
43
+
44
+ if receiver_data
45
+ FakeMethodExplanation.new(FakeMethodData.new(
46
+ receiver: receiver,
47
+ calls: receiver_data.calls,
48
+ stubbings: receiver_data.stubbings
49
+ ), describe_dry_method(receiver_data, method.name))
50
+ end
51
+ end
52
+
53
+ sig { params(double: Double).returns(DoubleData) }
54
+ def data_for_double(double)
55
+ DoubleData.new(
56
+ type: double.original_type,
57
+ double: double.dry_instance,
58
+ calls: Mocktail.cabinet.calls_for_double(double),
59
+ stubbings: Mocktail.cabinet.stubbings_for_double(double)
60
+ )
61
+ end
62
+
63
+ sig { params(double: Double).returns(DoubleExplanation) }
64
+ def double_explanation(double)
65
+ double_data = data_for_double(double)
66
+
67
+ DoubleExplanation.new(double_data, <<~MSG)
68
+ This is a fake `#{double.original_type.name}' instance.
69
+
70
+ It has these mocked methods:
71
+ #{double.dry_methods.sort.map { |method| " - #{method}" }.join("\n")}
72
+
73
+ #{double.dry_methods.sort.map { |method| describe_dry_method(double_data, method) }.join("\n")}
74
+ MSG
75
+ end
76
+
77
+ sig { params(type_replacement: TypeReplacement).returns(TypeReplacementData) }
78
+ def data_for_type_replacement(type_replacement)
79
+ TypeReplacementData.new(
80
+ type: type_replacement.type,
81
+ replaced_method_names: type_replacement.replacement_methods&.map(&:name)&.sort || [],
82
+ calls: Mocktail.cabinet.calls.select { |call|
83
+ call.double == type_replacement.type
84
+ },
85
+ stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
86
+ stubbing.recording.double == type_replacement.type
87
+ }
88
+ )
89
+ end
90
+
91
+ sig { params(type_replacement: TypeReplacement).returns(ReplacedTypeExplanation) }
92
+ def replaced_type_explanation(type_replacement)
93
+ type_replacement_data = data_for_type_replacement(type_replacement)
94
+
95
+ ReplacedTypeExplanation.new(type_replacement_data, <<~MSG)
96
+ `#{type_replacement.type}' is a #{type_replacement.type.class.to_s.downcase} that has had its methods faked.
97
+
98
+ It has these mocked methods:
99
+ #{type_replacement_data.replaced_method_names.map { |method| " - #{method}" }.join("\n")}
100
+
101
+ #{type_replacement_data.replaced_method_names.map { |method| describe_dry_method(type_replacement_data, method) }.join("\n")}
102
+ MSG
103
+ end
104
+
105
+ sig { params(double_data: T.any(DoubleData, TypeReplacementData), method: Symbol).returns(String) }
106
+ def describe_dry_method(double_data, method)
107
+ method_name = @stringifies_method_name.stringify(Call.new(
108
+ original_type: double_data.type,
109
+ singleton: double_data.type == double_data.double,
110
+ method: method
111
+ ))
112
+
113
+ [
114
+ @stringifies_call.stringify_multiple(
115
+ double_data.stubbings.map(&:recording).select { |call|
116
+ call.method == method
117
+ },
118
+ nonzero_message: "`#{method_name}' stubbings",
119
+ zero_message: "`#{method_name}' has no stubbings"
120
+ ),
121
+ @stringifies_call.stringify_multiple(
122
+ double_data.calls.select { |call|
123
+ call.method == method
124
+ },
125
+ nonzero_message: "`#{method_name}' calls",
126
+ zero_message: "`#{method_name}' has no calls"
127
+ )
128
+ ].join("\n")
129
+ end
130
+
131
+ sig { params(thing: Object).returns(NoExplanation) }
132
+ def no_explanation(thing)
133
+ NoExplanation.new(NoExplanationData.new(thing: thing),
134
+ "Unfortunately, Mocktail doesn't know what this thing is: #{thing.inspect}")
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+
3
+ module Mocktail
4
+ class GrabsOriginalMethodParameters
5
+ extend T::Sig
6
+
7
+ # Sorbet wraps the original method in a sig wrapper, so we need to unwrap it.
8
+ # The value returned from `owner.instance_method(method_name)` does not have
9
+ # the real parameters values available, as they'll have been erased
10
+ #
11
+ # If the method isn't wrapped by Sorbet, this will return the #instance_method,
12
+ # per usual
13
+ sig { params(method: T.nilable(T.any(UnboundMethod, Method))).returns(T::Array[T::Array[Symbol]]) }
14
+ def grab(method)
15
+ return [] unless method
16
+
17
+ if (wrapped_method = sorbet_wrapped_method(method))
18
+ wrapped_method.parameters
19
+ else
20
+ method.parameters
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ sig { params(method: T.any(UnboundMethod, Method)).returns(T.nilable(T::Private::Methods::Signature)) }
27
+ def sorbet_wrapped_method(method)
28
+ return unless defined?(::T::Private::Methods)
29
+
30
+ T::Private::Methods.signature_for_method(method)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+
3
+ require_relative "../../share/cleans_backtrace"
4
+ require_relative "../../share/bind"
5
+
6
+ module Mocktail
7
+ class DescribesUnsatisfiedStubbing
8
+ extend T::Sig
9
+
10
+ sig { void }
11
+ def initialize
12
+ @cleans_backtrace = T.let(CleansBacktrace.new, Mocktail::CleansBacktrace)
13
+ end
14
+
15
+ sig { params(dry_call: Mocktail::Call).returns(Mocktail::UnsatisfyingCall) }
16
+ def describe(dry_call)
17
+ UnsatisfyingCall.new(
18
+ call: dry_call,
19
+ other_stubbings: Mocktail.cabinet.stubbings.select { |stubbing|
20
+ Bind.call(dry_call.double, :==, stubbing.recording.double) &&
21
+ dry_call.method == stubbing.recording.method
22
+ },
23
+ backtrace: @cleans_backtrace.clean(Error.new).backtrace || []
24
+ )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+
3
+ require_relative "../../share/determines_matching_calls"
4
+
5
+ module Mocktail
6
+ class FindsSatisfaction
7
+ extend T::Sig
8
+
9
+ sig { void }
10
+ def initialize
11
+ @determines_matching_calls = T.let(DeterminesMatchingCalls.new, Mocktail::DeterminesMatchingCalls)
12
+ end
13
+
14
+ sig { params(dry_call: Call).returns(T.nilable(Stubbing[T.anything])) }
15
+ def find(dry_call)
16
+ Mocktail.cabinet.stubbings.reverse.find { |stubbing|
17
+ demo_config_times = stubbing.demo_config.times
18
+
19
+ @determines_matching_calls.determine(dry_call, stubbing.recording, stubbing.demo_config) &&
20
+ (demo_config_times.nil? || demo_config_times > stubbing.satisfaction_count)
21
+ }
22
+ end
23
+ end
24
+ end