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,515 @@
1
+ # Stubbing and verifying mocked methods
2
+
3
+ The [test doubles](support/glossary.md#test-double) created by Mocktail can be used to aid in the setup and
4
+ assertion of [isolated unit tests](support/glossary.md#isolated-unit-testing)
5
+ by offering [stub configuration](support/glossary.md#stub) and [spy
6
+ verification](support/glossary.md#spy), respectively.
7
+
8
+ A headline benefit of choosing Mocktail over other mocking libraries is that
9
+ once you know how to stub a method, you also know how to verify a method.
10
+ Because Mocktail's [Mocktail.stubs](support/api.md#mocktailstubs) and
11
+ [Mocktail.verify](support/api.md#mocktailverify) methods are the two most-used
12
+ methods in the library, and because they both enable variations of the same
13
+ behavior—`stubs` anticipates future calls to a dependency whereas `verify`
14
+ ensures past calls occurred—their APIs are similarly symmetrical. That means
15
+ both the `stubs` and `verify` methods share the same basic signature and all the
16
+ same options. Mocking is poorly understood by a lot of developers, so we took a
17
+ lot of care in designing an API that reflected their conceptual similarity
18
+ instead of forcing users to memorize a larger API surface.
19
+
20
+ ## Stubbing behavior
21
+
22
+ When you've mocked out a [dependency](support/glossary.md#dependency) of the
23
+ [subject you're testing](support/glossary.md#subject-under-test), you can use
24
+ Mocktail's [stubs](support/api.md#mocktailstubs) DSL method in your tests to
25
+ configure its methods to respond based on the arguments and blocks they're
26
+ passed.
27
+
28
+ In these examples, we'll look at an example dependency object with a few
29
+ instance methods we might want to stub.
30
+
31
+ ```ruby
32
+ class Bartop
33
+ def place_coaster(seat_position = 0)
34
+ # …
35
+ end
36
+
37
+ def clean_surface(with:)
38
+ # …
39
+ end
40
+ end
41
+ ```
42
+
43
+ And we'll work with a mock instance we can create with [Mocktail.of](support/api.md#mocktailof):
44
+
45
+ ```ruby
46
+ bartop = Mocktail.of(Bartop)
47
+ ```
48
+
49
+ Initially, `bartop` will return `nil` for any invocation of its faked methods,
50
+ but will still require arguments match their specified signature, raising
51
+ `ArgumentError` if they aren't provided:
52
+
53
+ ```
54
+ > bartop.clean_surface(with: :rag)
55
+ => nil
56
+ > bartop.clean_surface
57
+ => missing keyword: :with [Mocktail call: `clean_surface'] (ArgumentError)
58
+ ```
59
+
60
+ ### Simple, no-arg stubbing
61
+
62
+ Because `place_coaster`'s only parameter has a default value, the simplest
63
+ stubbing we can create is the no-arg case:
64
+
65
+ ```ruby
66
+ stubs { bartop.place_coaster }.with { :a_coaster }
67
+ ```
68
+
69
+ From then onward, calling the method without args will return `:a_coaster`:
70
+
71
+ ```
72
+ > bartop.place_coaster
73
+ => :a_coaster
74
+ > bartop.place_coaster(1)
75
+ => nil
76
+ > bartop.place_coaster()
77
+ => :a_coaster
78
+ ```
79
+
80
+ We can also stub the same method multiple times. Newer stubbings will override
81
+ older ones, as configured stubbings are matched against invocations on a
82
+ "last-in wins" basis:
83
+
84
+ ```ruby
85
+ > stubs { bartop.place_coaster }.with { :a_napkin }
86
+ => nil
87
+ > bartop.place_coaster
88
+ => :a_napkin
89
+ ```
90
+
91
+ You can also limit the number of times a stubbing can be satisfied by providing
92
+ a `times` keyword argument to `stubs`:
93
+
94
+ ```ruby
95
+ > stubs(times: 2) { bartop.place_coaster }.with { :gold_leaf }
96
+ => nil
97
+ > bartop.place_coaster
98
+ => :gold_leaf
99
+ > bartop.place_coaster
100
+ => :gold_leaf
101
+ > bartop.place_coaster
102
+ => :a_napkin
103
+ ```
104
+
105
+ As you can see above, as soon as the `:gold_leaf` stubbing hit its satisfaction
106
+ limit of `2`, `place_coaster` started once again responding with `:a_napkin`.
107
+
108
+ ## Stubbing with arguments
109
+
110
+ Of course, you wouldn't need a library if all you were stubbing was no-arg
111
+ methods, so let's start passing some values:
112
+
113
+ ```ruby
114
+ stubs { bartop.place_coaster(1) }.with { :coaster_1 }
115
+ stubs { bartop.place_coaster(2) }.with { :coaster_2 }
116
+ ```
117
+
118
+ And you can probably guess how these will behave:
119
+
120
+ ```ruby
121
+ > bartop.place_coaster(2)
122
+ => :coaster_2
123
+ > bartop.place_coaster(1)
124
+ => :coaster_1
125
+ > bartop.place_coaster(3)
126
+ => nil
127
+ ```
128
+
129
+ Keyword arguments work the same way as positional arguments:
130
+
131
+ ```ruby
132
+ stubs { bartop.clean_surface(with: :bleach) }.with { "👃" }
133
+ stubs { bartop.clean_surface(with: :rag) }.with { "✨" }
134
+
135
+ > bartop.clean_surface(with: :rag)
136
+ => "✨"
137
+ > bartop.clean_surface(with: :bleach)
138
+ => "👃"
139
+ > bartop.clean_surface(with: :toothbrush)
140
+ => nil
141
+ ```
142
+
143
+ ## Stubbing with inexact, dynamic arguments
144
+
145
+ When fully-isolated, tests will often provide exactly the
146
+ [values](support/glossary.md#value) that the subject will receive at every step,
147
+ and therefore will be able to provide a demonstration to `stubs` that passes the
148
+ exact arguments passed by the subject, or at least expected arguments that will
149
+ pass an equality check with the actual ones used by the subject.
150
+
151
+ But in more complex cases, you may need to configure a stubbing based on a
152
+ dynamic description of the arguments. Mocktail enables this with [argument
153
+ matchers](support/glossary.md#argument-matcher).
154
+
155
+ Here's a contrived example of Mocktail's built-in [matcher
156
+ API](support/api.md#matching-arguments-dynamically). A subject might pass a
157
+ random value to a dependency, which would definitely make it difficult
158
+ for a test to know the exact value being passed. Matchers could be used to
159
+ configure whether a stubbing or verification is satisfied.
160
+
161
+ Given this subject:
162
+
163
+ ```ruby
164
+ def leave_bathroom
165
+ @wash_hands.for_seconds(rand(5..10))
166
+ end
167
+ ```
168
+
169
+ A stubbing of `for_seconds` could work around the randomness by just matching
170
+ any value using [m.any](support/api.md#many):
171
+
172
+ ```ruby
173
+ stubs { |m| @wash_hands.for_seconds(m.any) }.with { :small_suds }
174
+
175
+ > @wash_hands.for_seconds(3)
176
+ => :small_suds
177
+ ```
178
+
179
+ Or it could enforce the type with [m.numeric](support/api.md#mnumeric):
180
+
181
+ ```ruby
182
+ stubs { |m| @wash_hands.for_seconds(m.numeric) }.with { :medium_suds }
183
+
184
+ > @wash_hands.for_seconds(30)
185
+ => :medium_suds
186
+ > @wash_hands.for_seconds("some time")
187
+ => nil
188
+ ```
189
+
190
+ Or, to be even more precise, a matcher like [m.that](support/api.md#mthat)—which takes a block param
191
+ validate the each
192
+ argument by itself being invoked
193
+
194
+ ```ruby
195
+ stubs { |m|
196
+ @wash_hands.for_seconds(m.that {|s| s.between?(5, 10) })
197
+ }.with { :big_suds }
198
+
199
+ > @wash_hands.for_seconds(7)
200
+ => :big_suds
201
+ > @wash_hands.for_seconds(1)
202
+ => nil
203
+ > @wash_hands.for_seconds(14)
204
+ => nil
205
+ ```
206
+
207
+ For more on the various matchers that ship with Mocktail as well as how to
208
+ create your own custom matchers, check out their [API
209
+ documentation](support/api.md#matching-arguments-dynamically).
210
+
211
+ There is a _lot_ more you can do with the
212
+ [Mocktail.stubs](support/api.md#mocktailstubs) method, but the basics shown
213
+ abouve should cover the vast majority of usage.
214
+
215
+ ## Verifying behavior
216
+
217
+ As mentioned at the top, Mocktail's mocks work as
218
+ [spies](support/glossary.md#spy), allowing users to verify that the
219
+ [subject](support/glossary.md#subject) invoked a method as expected. Mocktail
220
+ exposes this behavior through its [verify](support/api.md#mocktailverify) DSL
221
+ method. This section assumes you read and understand the
222
+ [stubs](#stubbing-behavior) section above, as the API is largely the same.
223
+
224
+ Before we dive in, there's a worthwhile discussion to be had comparing the
225
+ merits of using `stubs` and `verify`, because they weren't created equal.
226
+
227
+ [Pure functions](https://en.wikipedia.org/wiki/Pure_function), those who return
228
+ the same value for the same inputs and have no side effects, confer a lot of
229
+ benefits to developers: easier to comprehend, easier to compose, and easier to
230
+ test. It's generally worth striving to minimize the number of side effects
231
+ scattered throughout a codebase, but modern programming languages and frameworks
232
+ often make it very easy to write side-effect heavy code by failing to provide
233
+ meaningful return values, especially when I/O is concerned. Practicing
234
+ test-driven development with mocks, however, shines a bright light on side
235
+ effects in your [dependencies](support/glossary.md#dependency): each time you
236
+ call `verify`, you're introducing a side effect into your code.
237
+
238
+ As a result, it's possible (and in a sense, laudable) to only occasionally reach
239
+ for Mocktail's `verify` method. That said, Ruby doesn't lend itself especially
240
+ well to purely functional designs and, regardless, some number of side effects
241
+ are unavoidable for systems that interact with the outside world. And because
242
+ side effects are often very difficult to test (given the lack of a return
243
+ value), mocking libraries can make it very easy to test an interaction happens
244
+ as intended.
245
+
246
+ Suppose you have a subject that needs to call a dependency that has a side
247
+ effect and no return value (be wary of APIs that do both, violating
248
+ [command-query separation](support/glossary.md#command-query-separation)).
249
+
250
+ Let's make up an example of such a dependency:
251
+
252
+ ```ruby
253
+ class OrdersLimes
254
+ def order!(lime_count = 1, shipping: :overnight)
255
+ # …
256
+ end
257
+ end
258
+
259
+ orders_limes = Mocktail.of(OrdersLimes)
260
+ ```
261
+
262
+ ### Verifying a no-arg interaction
263
+
264
+ The simplest verification a test can make is of a dependent method with no
265
+ arguments. We can verify that `order!` was invoked like this:
266
+
267
+ ```ruby
268
+ verify { orders_limes.order! }
269
+ ```
270
+
271
+ But it hasn't been called yet! So `verify` will raise a
272
+ `Mocktail::VerificationError`:
273
+
274
+ ```
275
+ Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
276
+
277
+ order!
278
+
279
+ But it was never called.
280
+ ```
281
+
282
+ What if we try again? This time calling `order!` first:
283
+
284
+ ```ruby
285
+ > orders_limes.order!
286
+ => nil
287
+ > verify { orders_limes.order! }
288
+ => nil
289
+ ```
290
+
291
+ Nothing happened! Just as you'd expect. The verification passed so no action is
292
+ necessary and the test can proceed.
293
+
294
+ We can call `order!` an arbitrary number of times and verify it as many times as
295
+ we like. By default, `verify` only cares that the specified interaction occurred
296
+ at least once.
297
+
298
+ ### Verifying methods with arguments
299
+
300
+ When verifying an invocation with arguments, the same rules apply as for
301
+ stubbing: each actual positional and keyword argument is compared with those
302
+ specified in the `verify` [demonstration](support/glossary.md#demonstration)
303
+ using `==` or, optionally, an [argument
304
+ matchers](support/api.md#matching-arguments-dynamically).
305
+
306
+ Let's call `order!` a few times in different ways:
307
+
308
+ ```ruby
309
+ orders_limes.order!(3)
310
+ orders_limes.order!(50, shipping: :two_day)
311
+ orders_limes.order!(shipping: :ground)
312
+ ```
313
+
314
+ Now let's try a verification that we know will fail:
315
+
316
+ ```ruby
317
+ verify { orders_limes.order!(4, shipping: :ground) }
318
+ ```
319
+
320
+ This will fail as we'd expect, as well as printing out summaries of the prior
321
+ invocations:
322
+
323
+ ```ruby
324
+ Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
325
+
326
+ order!(4, shipping: :ground)
327
+
328
+ It was called differently 3 times:
329
+
330
+ order!(3)
331
+
332
+ order!(50, shipping: :two_day)
333
+
334
+ order!(shipping: :ground)
335
+ ```
336
+
337
+ Mocktail does its best to reconstruct a scrutible string for each invocation to
338
+ ease in debugging unexpected failures, but if that's enough, you can also
339
+ leverage its [Mocktail.calls](support/api.md#mocktailcalls) method to inspect
340
+ each invocation to `order!`, replete with references to each argument passed:
341
+
342
+ ```ruby
343
+ > Mocktail.calls(orders_limes, :order!)
344
+ =>
345
+ [#<Mocktail::Call:0x0000000104631af0
346
+ @args=[3],
347
+ @block=nil,
348
+ @double=#<Mocktail of OrdersLimes:0x00000001044974b0>,
349
+ @dry_type=#<Class for mocktail of OrdersLimes:0x000000010465e758>,
350
+ @kwargs={},
351
+ @method=:order!,
352
+ @original_method=#<UnboundMethod: OrdersLimes#order!(lime_count=..., shipping: ...),
353
+ @original_type=OrdersLimes,
354
+ @singleton=false>,
355
+ #<Mocktail::Call:0x0000000104652318
356
+ @args=[50],
357
+ @block=nil,
358
+ @double=#<Mocktail of OrdersLimes:0x00000001044974b0>,
359
+ @dry_type=#<Class for mocktail of OrdersLimes:0x000000010465e758>,
360
+ @kwargs={:shipping=>:two_day},
361
+ @method=:order!,
362
+ @original_method=#<UnboundMethod: OrdersLimes#order!(lime_count=..., shipping: ...),
363
+ @original_type=OrdersLimes,
364
+ @singleton=false>,
365
+ #<Mocktail::Call:0x00000001046512d8
366
+ @args=[],
367
+ @block=nil,
368
+ @double=#<Mocktail of OrdersLimes:0x00000001044974b0>,
369
+ @dry_type=#<Class for mocktail of OrdersLimes:0x000000010465e758>,
370
+ @kwargs={:shipping=>:ground},
371
+ @method=:order!,
372
+ @original_method=#<UnboundMethod: OrdersLimes#order!(lime_count=..., shipping: ...),
373
+ @original_type=OrdersLimes,
374
+ @singleton=false>]
375
+
376
+ # Inspecting the most recent call's keyword arguments:
377
+ > Mocktail.calls(orders_limes, :order!).last.kwargs
378
+ => {:shipping=>:ground}
379
+ ```
380
+
381
+ This is, hopefully, all you'd need to figure out why an expected invocation
382
+ failed a `verify` check unexpectedly.
383
+
384
+ ### Verifying a call happened a certain number of times
385
+
386
+ Just like `stubs`, `verify` has a `times` keyword argument. But, where `stubs`
387
+ will limit a stubbing to the number of `times` specified, `verify` will enforce
388
+ that exactly that numer of matching invocations took place.
389
+
390
+ This isn't something you'll need every day, but if you're paranoid about
391
+ erroneously making multiple lime orders, then you could ensure it was just
392
+ called once:
393
+
394
+ ```ruby
395
+ > orders_limes.order!(5, shipping: :two_day)
396
+ => nil
397
+ > orders_limes.order!(5, shipping: :two_day)
398
+ => nil
399
+ > verify(times: 1) { orders_limes.order!(5, shipping: :two_day) }
400
+ ```
401
+
402
+ As you might expect, this will raise a `VerificationError` because the method
403
+ was called twice in the specified way instead of once. The error message tries
404
+ to make this clear:
405
+
406
+ ```ruby
407
+ Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
408
+
409
+ order!(5, shipping: :two_day) [1 time]
410
+
411
+ But it was actually called this way 2 times.
412
+ ```
413
+
414
+ ### Adding matchers to a verification
415
+
416
+ Continuing the thread above, let's say you don't know or don't care what the
417
+ `shipping:` keyword argument was set to. For the purposes of the test, if that
418
+ doesn't matter and you just want to express that only a single order for `5`
419
+ limes was made, regardless of shipping method, you can use the
420
+ [m.any](support/api.md#many) just like we did in [the stubbing section
421
+ above](#stubbing-with-inexact-dynamic-arguments).
422
+
423
+ To make this point, let's call `order!` one more time with a different shipping
424
+ method:
425
+
426
+ ```ruby
427
+ > orders_limes.order!(5, shipping: :carrier_pigeon)
428
+ => nil
429
+ ```
430
+
431
+ Now we can adjust our `verify` call by using `m.any` for the `shipping` kwarg:
432
+
433
+ ```ruby
434
+ verify(times: 1) { |m| orders_limes.order!(5, shipping: m.any) }
435
+ ```
436
+
437
+ Because we'd called the method twice in the [immediately
438
+ previous](#verifying-a-call-happened-a-certain-number-of-times) and once more
439
+ just now. So we should expect Mocktail's error to find all _three_ matching
440
+ invocations:
441
+
442
+ ```ruby
443
+ Expected mocktail of `OrdersLimes#order!' to be called like: (Mocktail::VerificationError)
444
+
445
+ order!(5, shipping: any) [1 time]
446
+
447
+ But it was actually called this way 3 times.
448
+ ```
449
+
450
+ There it is! The expectation sees `shipping: any` and correctly counts that it
451
+ was invoked `3 times`.
452
+
453
+ ### Ignoring extraneous arguments entirely
454
+
455
+ Let's keep pulling the thread and continue the example above.
456
+
457
+ Suppose this isn't paranoid _enough_ for our tastes. Maybe the method supports
458
+ lots of additional optional arguments. And maybe we just _really really_ care
459
+ that the method was called once no matter what. We could do this in two ways:
460
+
461
+ 1. Verify that the method was called once, regardless of argument
462
+ 2. Split the verification in two: verify the call exactly as we expect, and assert
463
+ the call count is as we expect
464
+
465
+ In general, approach #2 is better: it expresses the two intentions separately,
466
+ which allows both to be made precisely.
467
+
468
+ If we'd been expecting `:carrier_pigeon` shipping all along, we could verify it
469
+ and then check `Mocktail.calls` to have the right number of invocations on
470
+ `:order!`:
471
+
472
+ ```ruby
473
+ > verify { orders_limes.order!(5, shipping: :carrier_pigeon) }
474
+ => nil
475
+ > assert_equal 1, Mocktail.calls(orders_limes, :order!).size
476
+ => 💥 asertion failed! Expected 1 but got 3
477
+ ```
478
+
479
+ If this is what you're trying to accomplish, this approach is not only more
480
+ precise in what it asserts, it expresses the test's intent more clearly to
481
+ future readers.
482
+
483
+ If, however, extraneous arguments are truly irrelevant from the perspective of
484
+ the test, approach #1 may be preferable. To enable this, you can pass
485
+ `ignore_extra_args: true`.
486
+
487
+ In our running example, we can omit all or some of the arguments and
488
+ `ignore_extra_args` will match every invocation, ignoring the value of their
489
+ other arguments. This way, we could specify that we wanted exactly one
490
+ invocation of `order!` via carrier pigeon, no matter how many limes were
491
+ ordered:
492
+
493
+ ```ruby
494
+ > verify(times: 1, ignore_extra_args: true) { orders_limes.order!(shipping: :carrier_pigeon) }
495
+ => nil
496
+ ```
497
+
498
+ For more options and complications, check out the full documentation of the
499
+ [verify](support/api.md#mocktailverify) API.
500
+
501
+ ## Pulling it all together
502
+
503
+ At this point, we've covered either Mocktail's [sorbet
504
+ setup](./installation_sorbet.md) or [untyped
505
+ install](./installation_untyped.md). You've learned how to instantiate mocks by
506
+ [dependency injection](tdd/poro/dependency_injection.md), [dependency
507
+ inception](tdd/poro/dependency_inception.md), or [class/module method
508
+ replacement](tdd/class_methods.md). And now you've been through the basics of
509
+ stubbing and verifying interactions with mocked methods. You've also
510
+ probably referenced the full [API documentation](support/api.md) and visited the [glossary of terms](support/glossary.md) a few times.
511
+
512
+ All that's left is to put it all together and write a test!
513
+
514
+ **When you're ready, let's [walk through a complete example test](example_test.md), guided by Mocktail.**
515
+