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,819 @@
1
+ # Mocktail's API
2
+
3
+ While the entire public API is listed in the [top-level module's source listing](/src/mocktail.rb),
4
+ but here are some more in-depth descriptions of all of Mocktail's public
5
+ methods.
6
+
7
+
8
+ * [Creating mocks](#creating-mocks)
9
+ * [Mocktail.of](#mocktailof)
10
+ * [Mocktail.of_next](#mocktailof_next)
11
+ * [Mocktail.of_next_with_count](#mocktailof_next_with_count)
12
+ * [Stubbing & verifying interactions](#stubbing--verifying-interactions)
13
+ * [Mocktail.stubs](#mocktailstubs)
14
+ * [Mocktail.verify](#mocktailverify)
15
+ * [Matching Arguments dynamically](#matching-arguments-dynamically)
16
+ * [Mocktail.matchers](#mocktailmatchers)
17
+ * [Built-in matchers](#built-in-matchers)
18
+ * [m.any](#many)
19
+ * [m.is_a](#mis_a)
20
+ * [m.includes](#mincludes)
21
+ * [m.matches](#mmatches)
22
+ * [m.not](#mnot)
23
+ * [m.numeric](#mnumeric)
24
+ * [m.that](#mthat)
25
+ * [Custom matchers](#custom-matchers)
26
+ * [Mocktail.captor](#mocktailcaptor)
27
+ * [Mocking class and module methods](#mocking-class-and-module-methods)
28
+ * [Mocktail.replace](#mocktailreplace)
29
+ * [Debugging](#debugging)
30
+ * [Mocktail.explain](#mocktailexplain)
31
+ * [Fake instances created by Mocktail](#fake-instances-created-by-mocktail)
32
+ * [Modules and classes with their methods replaced](#modules-and-classes-with-their-methods-replaced)
33
+ * [Methods on faked instances and replaced types](#methods-on-faked-instances-and-replaced-types)
34
+ * [Undefined methods](#undefined-methods)
35
+ * [Mocktail.explain_nils](#mocktailexplain_nils)
36
+ * [Mocktail.calls](#mocktailcalls)
37
+ * [Managing internal state](#managing-internal-state)
38
+ * [Mocktail.reset](#mocktailreset)
39
+
40
+ ## Creating mocks
41
+
42
+ ### Mocktail.of
43
+
44
+ `Mocktail.of(module_or_class)` takes a module or class and returns an instance
45
+ of an object with fake methods in place of all its instance methods which can
46
+ then be stubbed or verified.
47
+
48
+ ```ruby
49
+ class Clothes; end;
50
+ class Shoe < Clothes
51
+ def tie(laces)
52
+ end
53
+ end
54
+
55
+ shoe = Mocktail.of(Shoe)
56
+ shoe.instance_of?(Shoe) # => true
57
+ shoe.is_a?(Clothes) # => true
58
+ shoe.class == Shoe # => false!
59
+ shoe.to_s # => #<Mocktail of Shoe:0x00000001343b57b0>"
60
+ ```
61
+
62
+ ### Mocktail.of_next
63
+
64
+ `Mocktail.of_next(SomeClass)` takes a class and returns one mock (the
65
+ default) or an array of multiple mocks. It also effectively overrides the
66
+ behavior of that class's constructor to return those mock(s) in order and
67
+ finally restoring its previous behavior.
68
+
69
+ For example, if you wanted to test the `Notifier` class below:
70
+
71
+ ```ruby
72
+ class Notifier
73
+ def initialize
74
+ @mailer = Mailer.new
75
+ end
76
+
77
+ def notify(name)
78
+ @mailer.deliver!("Hello, #{name}")
79
+ end
80
+ end
81
+ ```
82
+
83
+ You could write a test like this:
84
+
85
+ ```ruby
86
+ def test_notifier
87
+ mailer = Mocktail.of_next(Mailer)
88
+ subject = Notifier.new
89
+
90
+ subject.notify("Pants")
91
+
92
+ verify { mailer.deliver!("Hello, Pants") }
93
+ end
94
+ ```
95
+
96
+ There's nothing wrong with creating mocks using `Mocktail.of` and passing them
97
+ to your subject some other way, but this approach allows you to write very terse
98
+ isolation tests without foisting additional indirection or dependency injection
99
+ in for your tests' sake.
100
+
101
+ #### Mocktail.of_next_with_count
102
+
103
+ In addition to telling Mocktail to overwrite `new` on a target Class to return
104
+ exactly one fake object before reverting to its original `new` method, you can
105
+ generate a set of fake objects this way.
106
+
107
+ ```ruby
108
+ class Dice
109
+ def roll
110
+ (1..6).to_a.sample
111
+ end
112
+ end
113
+
114
+ loaded_dice = Mocktail.of_next_with_count(Dice, 2)
115
+
116
+ stubs { loaded_dice[0].roll }.with { 1 }
117
+ stubs { loaded_dice[1].roll }.with { 1 }
118
+
119
+ # Then, over in your subject under test:
120
+ puts [Dice.new, Dice.new].sum { |dice| dice.roll }
121
+ #=> 2 # guaranteed snake eyes 🎲🎲
122
+
123
+ Dice.new.roll # Back to random rolls!
124
+ ```
125
+
126
+ `Mocktail.of_next(Dice, count: 2)` would also work in the above case, but isn't
127
+ able to be type-checked by Sorbet (because it can't change the return type to an
128
+ Array based on a non-`1` value of the `count` argument), so unless you
129
+ [disable Sorbet's runtime type checking](#im-seeing-a-typeerror-and-it-has-to-do-with-sorbet-and-i-dont-care-about-sorbet),
130
+ this will raise an exception.
131
+
132
+ ## Stubbing & verifying interactions
133
+
134
+ ### Mocktail.stubs
135
+
136
+ Configuring a fake method to take a certain action or return a particular value
137
+ is called "stubbing". To stub a call with a value, you can call `Mocktail.stubs`
138
+ (or just `stubs` if you've included `Mocktail::DSL`) and then specify an effect
139
+ that will be invoked whenever that call configuration is satisfied using `with`.
140
+
141
+ The API is very simple in the simple case:
142
+
143
+ ```ruby
144
+ class UserRepository
145
+ def find(id, debug: false); end
146
+
147
+ def transaction(&blk); end
148
+ end
149
+ ```
150
+
151
+ You could stub responses to a mock of the `UserRepository` like this:
152
+
153
+ ```ruby
154
+ user_repository = Mocktail.of(UserRepository)
155
+
156
+ stubs { user_repository.find(42) }.with { :a_user }
157
+ user_repository.find(42) # => :a_user
158
+ user_repository.find(43) # => nil
159
+ user_repository.find # => ArgumentError: wrong number of arguments (given 0, expected 1)
160
+ ```
161
+
162
+ The block passed to `stubs` is called the
163
+ "[demonstration](glossary.md#demonstration)", because it represents an example
164
+ of the kind of calls that Mocktail should match.
165
+
166
+ If you want to get fancy, you can use matchers to make your demonstration more
167
+ dynamic. For example, you could match any number with:
168
+
169
+ ```ruby
170
+ stubs { |m| user_repository.find(m.numeric) }.with { :another_user }
171
+ user_repository.find(41) # => :another_user
172
+ user_repository.find(42) # => :another_user
173
+ user_repository.find(43) # => :another_user
174
+ ```
175
+
176
+ Stubbings are last-in-wins, which is why the stubbing above would have
177
+ overridden the earlier-but-more-specific stubbing of `find(42)`.
178
+
179
+ A stubbing's effect can also be changed dynamically based on the actual call
180
+ that satisfied the demonstration by looking at the `call` block argument:
181
+
182
+ ```ruby
183
+ stubs { |m| user_repository.find(m.is_a(Integer)) }.with { |call|
184
+ {id: call.args.first}
185
+ }
186
+ user_repository.find(41) # => {id: 41}
187
+ # Since 42.5 is a Float, the earlier stubbing will win here:
188
+ user_repository.find(42.5) # => :another_user
189
+ user_repository.find(43) # => {id: 43}
190
+ ```
191
+
192
+ It's certainly more complex to think through, but if your stubbed method takes a
193
+ block, your demonstration can pass a block of its own and inspect or invoke it:
194
+
195
+ ```ruby
196
+ stubs {
197
+ user_repository.transaction { |block| block.call == {id: 41} }
198
+ }.with { :successful_transaction }
199
+
200
+ user_repository.transaction {
201
+ user_repository.find(41)
202
+ } # => :successful_transaction
203
+ user_repository.transaction {
204
+ user_repository.find(40)
205
+ } # => nil
206
+ ```
207
+
208
+ There are also several advanced options you can pass to `stubs` to control its
209
+ behavior.
210
+
211
+ `times` will limit the number of times a satisfied stubbing can have its effect:
212
+
213
+ ```ruby
214
+ stubs { |m| user_repository.find(m.any) }.with { :not_found }
215
+ stubs(times: 2) { |m| user_repository.find(1) }.with { :someone }
216
+
217
+ user_repository.find(1) # => :someone
218
+ user_repository.find(1) # => :someone
219
+ user_repository.find(1) # => :not_found
220
+ ```
221
+
222
+ `ignore_extra_args` will allow a demonstration to be considered satisfied even
223
+ if it fails to specify arguments and keyword arguments made by the actual call:
224
+
225
+ ```ruby
226
+ stubs { user_repository.find(4) }.with { :a_person }
227
+ user_repository.find(4, debug: true) # => nil
228
+
229
+ stubs(ignore_extra_args: true) { user_repository.find(4) }.with { :b_person }
230
+ user_repository.find(4, debug: true) # => :b_person
231
+ ```
232
+
233
+ And `ignore_block` will similarly allow a demonstration to not concern itself
234
+ with whether an actual call passed the method a block—it's satisfied either way:
235
+
236
+ ```ruby
237
+ stubs { user_repository.transaction }.with { :transaction }
238
+ user_repository.transaction {} # => nil
239
+
240
+ stubs(ignore_block: true) { user_repository.transaction }.with { :transaction }
241
+ user_repository.transaction {} # => :transaction
242
+ ```
243
+
244
+ ### Mocktail.verify
245
+
246
+ In practice, we've found that we stub far more responses than we explicitly
247
+ verify a particular call took place. That's because our code normally returns
248
+ some observable value that is _influenced_ by our dependencies' behavior, so
249
+ adding additional assertions that they be called would be redundant. That
250
+ said, for cases where a dependency doesn't return a value but just has a
251
+ necessary side effect, the `verify` method exists (and like `stubs` is included
252
+ in `Mocktail::DSL`).
253
+
254
+ Once you've gotten the hang of stubbing, you'll find that the `verify` method is
255
+ intentionally very similar. They almost rhyme.
256
+
257
+ For this example, consider an `Auditor` class that our code might need to call
258
+ to record that certain actions took place.
259
+
260
+ ```ruby
261
+ class Auditor
262
+ def record!(message, user_id:, action: nil); end
263
+ end
264
+ ```
265
+
266
+ Once you've created a mock of the `Auditor`, you can start verifying basic
267
+ calls:
268
+
269
+ ```ruby
270
+ auditor = Mocktail.of(Auditor)
271
+
272
+ verify { auditor.record!("hello", user_id: 42) }
273
+ # => raised Mocktail::VerificationError
274
+ # Expected mocktail of Auditor#record! to be called like:
275
+ #
276
+ # record!("hello", user_id: 42)
277
+ #
278
+ # But it was never called.
279
+ ```
280
+
281
+ Wups! Verify will blow up whenever a matching call hasn't occurred, so it
282
+ should be called after you've invoked your subject under test along with any
283
+ other assertions you have.
284
+
285
+ If we make a call that satisfies the `verify` call's demonstration, however, you
286
+ won't see that error:
287
+
288
+ ```ruby
289
+ auditor.record!("hello", user_id: 42)
290
+
291
+ verify { auditor.record!("hello", user_id: 42) } # => nil
292
+ ```
293
+
294
+ There, nothing happened! Just like any other assertion library, you only hear
295
+ from `verify` when verification fails.
296
+
297
+ Just like with `stubs`, you can any built-in or custom matchers can serve as
298
+ garnishes for your demonstration:
299
+
300
+ ```ruby
301
+ auditor.record!("hello", user_id: 42)
302
+
303
+ verify { |m| auditor.record!(m.is_a(String), user_id: m.numeric) } # => nil
304
+ # But this will raise a VerificationError:
305
+ verify { |m| auditor.record!(m.is_a(String), user_id: m.that { |arg| arg > 50}) }
306
+ ```
307
+
308
+ When you pass a block to your demonstration, it will be invoked with any block
309
+ that was passed to the actual call to the mock. Truthy responses will satisfy
310
+ the verification and falsey ones will fail:
311
+
312
+ ```ruby
313
+ auditor.record!("ok", user_id: 1) { Time.new }
314
+
315
+ verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Time) } } # => nil
316
+ # But this will raise a VerificationError:
317
+ verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Date) } }
318
+ ```
319
+
320
+ `verify` supports the same options as `stubs`:
321
+
322
+ * `times` will require the demonstrated call happened exactly `times` times (by
323
+ default, the call has to happen 1 or more times)
324
+ * `ignore_extra_args` will allow the demonstration to forego specifying optional
325
+ arguments while still being considered satisfied
326
+ * `ignore_block` will similarly allow the demonstration to forego specifying a
327
+ block, even if the actual call receives one
328
+
329
+ Note that if you want to verify a method _wasn't_ called at all or called a
330
+ specific number of times—especially if you don't care about the parameters, you
331
+ may want to look at the [Mocktail.calls()](#mocktailcalls) API.
332
+
333
+ ## Matching arguments dynamically
334
+
335
+ ### Mocktail.matchers
336
+
337
+ You'll probably never need to call `Mocktail.matchers` directly, because it's
338
+ the object that is passed to every demonstration block passed to `stubs` and
339
+ `verify`. By default, a stubbing (e.g. `stubs { email.send("text") }`) is only
340
+ considered satisfied if every argument passed to an actual call was passed an
341
+ `==` check. Matchers allow us to relax or change that constraint for both
342
+ regular arguments and keyword arguments so that our demonstrations can match
343
+ more kinds of method invocations.
344
+
345
+ Matchers allow you to specify stubbings and verifications that look like this:
346
+
347
+ ```ruby
348
+ stubs { |m| email.send(m.is_a(String)) }.with { "I'm an email" }
349
+ ```
350
+
351
+ #### Built-in matchers
352
+
353
+ These matchers come out of the box:
354
+
355
+ ##### m.any
356
+
357
+ `m.any` - Will match any value (even nil) in the given argument position or
358
+ keyword
359
+
360
+ ##### m.is_a
361
+
362
+ `m.is_a(type)` - Will match when its `type` passes an `is_a?` check against the
363
+ actual argument
364
+
365
+ ##### m.includes
366
+
367
+ `m.includes(thing, [**more_things])` - Will match when all of its arguments are
368
+ contained by the corresponding argument—be it a string, array, hash, or anything
369
+ that responds to `includes?`
370
+
371
+ ##### m.matches
372
+
373
+ `m.matches(pattern)` - Will match when the provided string or pattern passes a
374
+ `match?` test on the corresponding argument; usually used to match strings that
375
+ contain a particular substring or pattern, but will work with any argument that
376
+ responds to `match?`
377
+
378
+ ##### m.not
379
+
380
+ `m.not(thing)` - Will only match when its argument _does not_ equal (via `!=`)
381
+ the actual argument
382
+
383
+ ##### m.numeric
384
+
385
+ `m.numeric` - Will match when the actual argument is an instance of `Integer`,
386
+ `Float`, or (if loaded) `BigDecimal`
387
+
388
+ ##### m.that
389
+
390
+ `m.that { |arg| … }` - Takes a block that will receive the actual argument. If
391
+ the block returns truthy, it's considered a match; otherwise, it's not a match.
392
+
393
+ #### Custom matchers
394
+
395
+ If you want to write your own matchers, check out [the source for
396
+ examples](lib/mocktail/matchers/includes.rb). Once you've implemented a class,
397
+ just pass it to `Mocktail.register_matcher` in your test helper.
398
+
399
+ ```ruby
400
+ class MyAwesomeMatcher < Mocktail::Matchers::Base
401
+ def self.matcher_name
402
+ :awesome
403
+ end
404
+
405
+ def match?(actual)
406
+ "#{@expected}✨" == actual
407
+ end
408
+ end
409
+
410
+ Mocktail.register_matcher(MyAwesomeMatcher)
411
+ ```
412
+
413
+ Then, a stubbing like this:
414
+
415
+ ```ruby
416
+ stubs { |m| user_repository.find(m.awesome(11)) }.with { :awesome_user }
417
+
418
+ user_repository.find("11")) # => nil
419
+ user_repository.find("11✨")) # => :awesome_user
420
+ ```
421
+
422
+ ### Mocktail.captor
423
+
424
+ An argument captor is a special kind of matcher… really, it's a matcher factory.
425
+ Suppose you have a `verify` call for which one of the expected arguments is
426
+ _really_ complicated. Since `verify` tends to be paired with fire-and-forget
427
+ APIs that are being invoked for the side effect, this is a pretty common case.
428
+ You want to be able to effectively snag that value and then run any number of
429
+ specific assertions against it.
430
+
431
+ That's what `Mocktail.captor` is for. It's easiest to make sense of this by
432
+ example. Given this `BigApi` class that's presumably being called by your
433
+ subject at the end of a lot of other work building up a payload:
434
+
435
+ ```ruby
436
+ class BigApi
437
+ def send(payload); end
438
+ end
439
+ ```
440
+
441
+ You could capture the value of that payload as part of the verification of the
442
+ call:
443
+
444
+ ```ruby
445
+ big_api = Mocktail.of(BigApi)
446
+
447
+ big_api.send({imagine: "that", this: "is", a: "huge", object: "!"})
448
+
449
+ payload_captor = Mocktail.captor
450
+ verify { big_api.send(payload_captor.capture) } # => nil!
451
+ ```
452
+
453
+ The `verify` above will pass because _a_ call did happen, but we haven't
454
+ asserted anything beyond that yet. What really happened is that
455
+ `payload_captor.capture` actually returned a matcher that will return true for
456
+ any argument _while also sneakily storing a copy of the argument value_.
457
+
458
+ That's why we instantiated `payload_captor` with `Mocktail.captor` outside the
459
+ demonstration block, so we can inspect its `value` after the `verify` call:
460
+
461
+ ```ruby
462
+ payload_captor = Mocktail.captor
463
+ verify { big_api.send(payload_captor.capture) } # => nil!
464
+
465
+ payload = payload_captor.value # {:imagine=>"that", :this=>"is", :a=>"huge", :object=>"!"}
466
+ assert_equal "huge", payload[:a]
467
+ ```
468
+
469
+ ## Mocking class and module methods
470
+
471
+ ### Mocktail.replace
472
+
473
+ Mocktail was written to support isolated test-driven development, which usually
474
+ results in a lot of boring classes and instance methods. But sometimes you need
475
+ to mock methods on classes or modules, and we support that too.
476
+
477
+ When you call `Mocktail.replace(type)`, all of the methods defined on the
478
+ provided type are replaced with fake methods available for stubbing and
479
+ verification. It's really that simple.
480
+
481
+ For example, if our `Bartender` class has a class method:
482
+
483
+ ```ruby
484
+ class Bartender
485
+ def self.cliche_greeting
486
+ ["It's 5 o'clock somewhere!", "Norm!"].sample
487
+ end
488
+ end
489
+ ```
490
+
491
+ We can replace the behavior of the overall class, and then stub how we'd like it
492
+ to respond, in our test:
493
+
494
+ ```ruby
495
+ Mocktail.replace(Bartender)
496
+ stubs { Bartender.cliche_greeting }.with { "Norm!" }
497
+ ```
498
+
499
+ [**Obligatory warning:** Mocktail does its best to ensure that other threads
500
+ won't be affected when you replace the globally-referenceable methods on a type,
501
+ but your mileage may very! Singleton methods are global and code that
502
+ introspects or invokes a replaced method in a peculiar-enough way could lead to
503
+ hard-to-track down bugs. (If this concerns you, then the fact that class methods
504
+ are effectively global state may be a great reason not to rely too heavily on
505
+ them!)]
506
+
507
+ ## Debugging
508
+
509
+ ### Mocktail.explain
510
+
511
+ Test debugging is hard enough when there _aren't_ fake objects flying every
512
+ which way, so Mocktail tries to make it a little easier on you. In addition to
513
+ returning useful messages throughout the API, the gem also includes an
514
+ introspection method `Mocktail.explain(thing)`, which returns a human-readable
515
+ `message` and a `reference` object with useful attributes (that vary depending
516
+ on the type of fake `thing` you pass in.
517
+
518
+ Below are some things `explain()` can do.
519
+
520
+ #### Fake instances created by Mocktail
521
+
522
+ Any instances created by `Mocktail.of` or `Mocktail.of_next` can be passed to
523
+ `Mocktail.explain`, and they will list out all the calls and stubbings made for
524
+ each of their fake methods.
525
+
526
+ Suppose these interactions have occurred:
527
+
528
+ ```ruby
529
+ ice_tray = Mocktail.of(IceTray)
530
+
531
+ Mocktail.stubs { ice_tray.fill(:tap_water, 30) }.with { :some_ice }
532
+
533
+ ice_tray.fill(:tap_water, 50)
534
+ ```
535
+
536
+ You can interrogate what's going on with the fake instance by passing it to
537
+ `explain`:
538
+
539
+ ```ruby
540
+ explanation = Mocktail.explain(ice_tray)
541
+
542
+ explanation.reference.type #=> IceTray
543
+ explanation.reference.double #=> The ice_tray instance
544
+ explanation.reference.calls #=> details on each invocation of each method
545
+ explanation.reference.stubbings #=> all stubbings configured for each method
546
+ ```
547
+
548
+ Calling `explanation.message` will return:
549
+
550
+ ```
551
+ This is a fake `IceTray' instance.
552
+
553
+ It has these mocked methods:
554
+ - fill
555
+
556
+ `IceTray#fill' stubbings:
557
+
558
+ fill(:tap_water, 30)
559
+
560
+ `IceTray#fill' calls:
561
+
562
+ fill(:tap_water, 50)
563
+
564
+ ```
565
+
566
+ #### Modules and classes with their methods replaced
567
+
568
+ If you've called `Mocktail.replace()` on a class or module, it can also be
569
+ passed to `Mocktail.explain()` for a summary of all the stubbing configurations
570
+ and calls made against its faked methods for the currently running thread.
571
+
572
+ Imagine a `Shop` class with `self.open!` and `self.close!` methods:
573
+
574
+ ```ruby
575
+ Mocktail.replace(Shop)
576
+
577
+ stubs { |m| Shop.open!(m.numeric) }.with { :a_bar }
578
+
579
+ Shop.open!(42)
580
+
581
+ Shop.close!(42)
582
+
583
+ explanation = Mocktail.explain(Shop)
584
+
585
+ explanation.reference.type #=> Shop
586
+ explanation.reference.replaced_method_names #=> [:close!, :open!]
587
+ explanation.reference.calls #=> details on each invocation of each method
588
+ explanation.reference.stubbings #=> all stubbings configured for each method
589
+ ```
590
+
591
+ And `explanation.message` will return:
592
+
593
+ ```ruby
594
+ `Shop' is a class that has had its methods faked.
595
+
596
+ It has these mocked methods:
597
+ - close!
598
+ - open!
599
+
600
+ `Shop.close!' has no stubbings.
601
+
602
+ `Shop.close!' calls:
603
+
604
+ close!(42)
605
+
606
+ close!(42)
607
+
608
+ `Shop.open!' stubbings:
609
+
610
+ open!(numeric)
611
+
612
+ open!(numeric)
613
+
614
+ `Shop.open!' calls:
615
+
616
+ open!(42)
617
+
618
+ open!(42)
619
+ ```
620
+
621
+ #### Methods on faked instances and replaced types
622
+
623
+ In addition to passing the test double, you can also pass a reference to any
624
+ fake method created by Mocktail to `Mocktail.explain`:
625
+
626
+ ```ruby
627
+ ice_tray = Mocktail.of(IceTray)
628
+
629
+ ice_tray.fill(:chilled, 50)
630
+
631
+ explanation = Mocktail.explain(ice_tray.method(:fill))
632
+
633
+ explanation.reference.receiver #=> a reference to the `ice_tray` instance
634
+ explanation.reference.calls #=> details on each invocation of the method
635
+ explanation.reference.stubbings #=> all stubbings configured for the method
636
+ ```
637
+
638
+ The above may be handy in cases where you want to assert the number of calls of
639
+ a method outside the `Mocktail.verify` API:
640
+
641
+ ```ruby
642
+ assert_equal 1, explanation.reference.calls.size
643
+ ```
644
+
645
+ The explanation will also contain a `message` like this:
646
+
647
+ ```
648
+ `IceTray#fill' has no stubbings.
649
+
650
+ `IceTray#fill' calls:
651
+
652
+ fill(:chilled, 50)
653
+ ```
654
+
655
+ Replaced class methods can also be passed to `explain()`, so something like
656
+ `Mocktail.explain(Shop.method(:open!))` from the earlier example would also work
657
+ (with `Shop` being the `receiver` on the explanation's `reference`).
658
+
659
+ #### Undefined methods
660
+
661
+ There's no API for this one, but Mocktail also offers explanations for methods
662
+ that don't exist yet. You'll see this error message whenever you try to call a
663
+ method that doesn't exist on a test double. The message is designed to
664
+ facilitate "paint-by-numbers" TDD, by including a sample definition of the
665
+ method you had attempted to call that can be copy-pasted into a source listing:
666
+
667
+ ```ruby
668
+ class IceTray
669
+ end
670
+
671
+ ice_tray = Mocktail.of(IceTray)
672
+
673
+ ice_tray.fill(:water_type, 30)
674
+ # => No method `IceTray#fill' exists for call: (NoMethodError)
675
+ #
676
+ # fill(:water_type, 30)
677
+ #
678
+ # Need to define the method? Here's a sample definition:
679
+ #
680
+ # def fill(water_type, arg)
681
+ # end
682
+ ```
683
+
684
+ From there, you can just copy-paste the provided method stub as a starting point
685
+ for your new method:
686
+
687
+ ```ruby
688
+ class IceTray
689
+ def fill(water_type, amount)
690
+ end
691
+ end
692
+ ```
693
+
694
+ ### Mocktail.explain_nils
695
+
696
+ Is a faked method returning `nil` and you don't understand why?
697
+
698
+ By default, methods faked by Mocktail will return `nil` when no stubbing is
699
+ satisfied. A frequent frustration, therefore, is when the way `stubs {}.with {}`
700
+ is configured does not satisfy a call the way you expected. To try to make
701
+ debugging this a little bit easier, the gem provides a top-level
702
+ `Mocktail.explain_nils` method that will return an array of summaries of every
703
+ call to a faked method that failed to satisfy any stubbings.
704
+
705
+ For example, suppose you stub this `fill` method like so:
706
+
707
+ ```ruby
708
+ ice_tray = Mocktail.of(IceTray)
709
+
710
+ stubs { ice_tray.fill(:tap_water, 30) }.with { :normal_ice }
711
+ ```
712
+
713
+ But then you find that your subject under test is just getting `nil` back and
714
+ you don't understand why:
715
+
716
+ ```ruby
717
+ def prep
718
+ ice = ice_tray.fill(:tap_water, 50)
719
+ glass.add(ice) # => why is `ice` nil?!
720
+ end
721
+ ```
722
+
723
+ Whenever you're confused by a nil, you can call `Mocktail.explain_nils` for an
724
+ array containing `UnsatisfyingCallExplanation` objects (one for each call to
725
+ a faked method that did not satisfy any configured stubbings).
726
+
727
+ The returned explanation objects will include both a `reference` object to
728
+ explore as well a summary `message`:
729
+
730
+ ```ruby
731
+ def prep
732
+ ice = ice_tray.fill(:tap_water, 50)
733
+ puts Mocktail.explain_nils.first.message
734
+ glass.add(ice)
735
+ end
736
+ ```
737
+
738
+ Which will print:
739
+
740
+ ```
741
+ This `nil' was returned by a mocked `IceTray#fill' method
742
+ because none of its configured stubbings were satisfied.
743
+
744
+ The actual call:
745
+
746
+ fill(:tap_water, 50)
747
+
748
+ The call site:
749
+
750
+ /path/to/your/code.rb:42:in `prep'
751
+
752
+ Stubbings configured prior to this call but not satisfied by it:
753
+
754
+ fill(:tap_water, 30)
755
+ ```
756
+
757
+ The `reference` object will have details of the `call` itself, an array of
758
+ `other_stubbings` defined on the faked method, and a `backtrace` to determine
759
+ which call site produced the unexpected `nil` value.
760
+
761
+ ### Mocktail.calls
762
+
763
+ When practicing test-driven development, you may want to ensure that a
764
+ dependency wasn't called at all. To provide a terse way to express this,
765
+ Mocktail offers a top-level `calls(double, method_name = nil)` method that
766
+ returns an array of the calls to the mock (optionally filtered to a
767
+ particular method name) in the order they were called.
768
+
769
+ Suppose you were writing a test of this method for example:
770
+
771
+ ```ruby
772
+ def import_users
773
+ users_response = @gets_users.get
774
+ if users_response.success?
775
+ @upserts_users.upsert(users_response.data)
776
+ end
777
+ end
778
+ ```
779
+
780
+ A test case of the negative branch of that `if` statement (when `success?` is
781
+ false) might simply want to assert that `@upserts_users.upsert` wasn't called at
782
+ all, regardless of its parameters.
783
+
784
+ The easiest way to do this is to use `Mocktail.calls()` method, which is an
785
+ alias of [Mocktail.explain(double).reference.calls](#mocktailexplain) that can
786
+ filter to a specific method name. In the case of a test of the above method, you
787
+ could assert:
788
+
789
+ ```ruby
790
+ # Assert that the `upsert` method on the mock was never called
791
+ assert_equal 0, Mocktail.calls(@upserts_users, :upsert).size
792
+
793
+ # Assert that NO METHODS on the mock were called at all:
794
+ assert_equal 0, Mocktail.calls(@upserts_users).size
795
+ ```
796
+
797
+ If you're interested in doing more complicated introspection in the nature of
798
+ the calls, their ordering, and so forth, the `calls` method will return
799
+ `Mocktail::Call` values with the args, kwargs, block, and information about the
800
+ original class and method being mocked.
801
+
802
+ (While this behavior can technically be accomplished with `verify(times: 0) { …
803
+ }`, it's verbose and error prone to do so. Because `verify` is careful to only
804
+ assert exact argument matches, it can get pretty confusing to remember to tack
805
+ on `ignore_extra_args: true` and to call the method with zero args to cover all
806
+ cases.)
807
+
808
+ ## Managing internal state
809
+
810
+ ### Mocktail.reset
811
+
812
+ This one's simple: you probably want to call `Mocktail.reset` after each test,
813
+ but you _definitely_ want to call it if you're using `Mocktail.replace` or
814
+ `Mocktail.of_next` anywhere, since those will affect state that is shared across
815
+ tests.
816
+
817
+ Calling reset in a `teardown` or `after(:each)` hook will also improve the
818
+ usefulness of messages returned by `Mocktail.explain` and
819
+ `Mocktail.explain_nils`.