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,47 @@
1
+ # Using mocks to fix a broken test you can't understand
2
+
3
+ Suppose you have a large suite of tests and one of them just broke the build.
4
+ You didn't write this test or the thing it's testing. You just know you've gotta
5
+ figure out a way to fix it. The test is sprawling, confusing, and unclear. The
6
+ code it's testing isn't much better. You could fight to understand what is going
7
+ on, ascertain why things are breaking, and then fix it in a way that left things
8
+ better than you found them, but your first impression is that could take you
9
+ _days_ to accomplish.
10
+
11
+ A thought occurs: you could make the test go green by replacing the source of
12
+ the error with a mock and then stubbing whatever responses might be necessary
13
+ to make the test pass again.
14
+
15
+ Which is how you wound up installing Mocktail and then clicking through its
16
+ silly choose-your-own-adventure README, and here we are.
17
+
18
+ As discussed elsewhere, Mocktail is designed with [test-driven
19
+ development](../tdd.md) of _new_ tests and classes in mind, and not as a tool to
20
+ assist in legacy rescue, fixing broken tests, or otherwise coping with existing
21
+ complexity.
22
+
23
+ Situations like the one described above are sadly _very common_, but there is no
24
+ alternative to putting in the work to grapple with the complexity and tame it
25
+ enough to understand _why_ a test is failing and then ensuring the fix doesn't
26
+ undermine the purpose of the test. Put differently, if you were to make the test
27
+ pass by plugging a leak in the dike with a mock object, there's no way to know
28
+ that the originating failure wasn't indicating an actual real-world bug or
29
+ problem in the code. Additionally, the comprehensibility and value of the test
30
+ would almost certainly be left worse off after the fix is applied.
31
+
32
+ Cases like this are one of the reason most experienced developers hate dealing
33
+ with mock objects, because they're so often made to paper over underlying
34
+ problems by abusing their ability to make the computer lie to you.
35
+
36
+ As a result, if you have an existing test that wasn't built from the ground-up
37
+ as an isolated unit test that used [test
38
+ doubles](../support/glossary.md#test-double) to achieve isolation from the
39
+ [subject](../support/glossary.md#subject-under-test)'s
40
+ [dependencies](../support/glossary.md#dependency), adding mocks to it is almost
41
+ always a worse long-term solution than disabling the test entirely.
42
+
43
+ **If you've heard enough, you can go back and consider [non-TDD use cases for Mocktail](../other_uses.md).**
44
+
45
+ **Or if you're finally ready to walk the golden path, you can revisit [Mocktail as a TDD tool](../tdd.md).**
46
+
47
+
@@ -0,0 +1,151 @@
1
+ # Mocking out network requests by faking HTTP
2
+
3
+ Mocktail does nothing to make faking HTTP requests easy. And if you care to try,
4
+ know that it also does nothing to ensure mocked requests behave consistently
5
+ across various HTTP libraries and gems (`Net::HTTP`, faraday, httparty, etc.).
6
+ In fact, other gems are designed to do this and only this, most notably
7
+ [webmock](https://github.com/bblimke/webmock).
8
+
9
+ But, if you're reading this, there's a _small chance_ you're thinking about
10
+ faking out the network in the context of something you're conceiving of as a
11
+ unit test, and that's why you're looking to your mocking library for an answer.
12
+ If that's the case, here's a quick note on why you might be trying to apply
13
+ the wrong solution to the problem.
14
+
15
+ Suppose you're writing a unit test and trying to control for the network and
16
+ thinking about using a library like Mocktail as a result. To illustrate: imagine
17
+ you're testing a method that makes a network request and does one thing if the
18
+ response succeeds and another thing to handle failures.
19
+
20
+ You might want to write these two test cases:
21
+
22
+ ```ruby
23
+ def test_success
24
+ result = @subject.hack_computer("/school_sprinklers")
25
+
26
+ assert_equal "Mess with the best, die like the rest", result
27
+ end
28
+
29
+ def test_failure
30
+ assert_raises(CrashOverrideError) do
31
+ @subject.hack_computer("/the_gibson")
32
+ end
33
+ end
34
+ ```
35
+
36
+ But you may not have an easy way to force the first test case to always exercise
37
+ the happy path in which the request succeeds. Similarly, you would need a way to
38
+ ensure the other test case to consistently travels the sad path wherein the
39
+ request fails. Short of spinning up a fake HTTP service, you don't have a lot
40
+ of options, especially if your implementation looks anything like this:
41
+
42
+ ```ruby
43
+ class Computer
44
+ SICK_BURN = "Mess with the best, die like the rest"
45
+
46
+ def hack_computer(target)
47
+ uri = URI.parse("https://example.com#{target}")
48
+ req = Net::HTTP::Post.new(uri)
49
+
50
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
51
+ http.request(req) do |res|
52
+ if Net::HTTPSuccess === res
53
+ return SICK_BURN
54
+ else
55
+ raise CrashOverrideError
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
63
+ This case is so simple that it may strain credulity, but instead of trying to
64
+ introduce a network-layer mock to your application-layer concern, you might
65
+ consider writing a [wrapper](../support/glossary.md#wrapper-object) around the
66
+ `Net::HTTP` API that you don't control and can't change that your application
67
+ can invoke wherever it needs to make a network request. Then, instead of your
68
+ test needing to mock out the entire networking stack, it just needs to _mock out
69
+ the wrapper_ instead.
70
+
71
+ Here's what the refactored [subject](../support/glossary.md#subject-under-test)
72
+ could look like if we extracted its usage of `Net::HTTP` into an `Http` wrapper
73
+ that we own:
74
+
75
+ ```ruby
76
+ class Computer
77
+ SICK_BURN = "Mess with the best, die like the rest"
78
+
79
+ def initialize
80
+ @http = Http.new
81
+ end
82
+
83
+ def hack_computer(target)
84
+ if @http.post("https://example.com#{target}").success?
85
+ return SICK_BURN
86
+ else
87
+ raise CrashOverrideError
88
+ end
89
+ end
90
+ end
91
+
92
+ class Http
93
+ Result = Data.define(:json, :success?)
94
+
95
+ def post(url)
96
+ uri = URI.parse(url)
97
+ req = Net::HTTP::Post.new(uri)
98
+
99
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
100
+ http.request(req) do |res|
101
+ return Result.new(
102
+ data: JSON.parse(res.body),
103
+ success?: Net::HTTPSuccess === res
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
109
+ ```
110
+
111
+ Now, the test will be much easier to implement, because instead of trying to
112
+ figure out how to mock out all of HTTP, we're simply mocking out an object that
113
+ quacks just like any other object—leaving the fact that it crosses a spooky
114
+ network boundary as a mere implementation detail that `Computer`'s test doesn't
115
+ need to worry about.
116
+
117
+ Now, a complete test could fully cover the subject with minimally-invasive
118
+ mocking:
119
+
120
+ ```ruby
121
+ def initialize
122
+ @http = Mocktail.of_next(Http)
123
+
124
+ @subject = Computer.new
125
+ end
126
+
127
+ def test_success
128
+ stubs { Http.post("/school_sprinklers") }.with { Computer::Result.new(data: nil, success?: true) }
129
+
130
+ result = @subject.hack_computer("/school_sprinklers")
131
+
132
+ assert_equal "Mess with the best, die like the rest", result
133
+ end
134
+
135
+ def test_failure
136
+ stubs { Http.post("/the_gibson") }.with { Computer::Result.new(data: nil, success?: false) }
137
+
138
+ assert_raises(CrashOverrideError) do
139
+ @subject.hack_computer("/the_gibson")
140
+ end
141
+ end
142
+ ```
143
+
144
+ Anyway, that's why you _probably_ don't want to fake out an HTTP interaction
145
+ directly with Mocktail and should instead look to a purpose-built gem like
146
+ [webmock](https://github.com/bblimke/webmock) or else consider alternative
147
+ designs that make the code in question easier to test.
148
+
149
+ **If you've heard enough, you can go back and consider [non-TDD use cases for Mocktail](../other_uses.md).**
150
+
151
+ **Or if you're finally ready to walk the golden path, you can revisit [Mocktail as a TDD tool](../tdd.md).**
@@ -0,0 +1,161 @@
1
+ # Mocking methods on the subject being tested
2
+
3
+ Once in a while, it may seem desirable to fake out a method on the [subject
4
+ under test](../support/glossary.md#subject-under-test).
5
+
6
+ The scenario usually looks something like this:
7
+
8
+ ```ruby
9
+ class User < ApplicationRecord
10
+ # The method under test:
11
+ def best_promo
12
+ promos = CouponCode.active.flat_map { |coupon_code|
13
+ eligible_promotions(coupon_code)
14
+ }.max_by { |promo|
15
+ promo.percent_discount
16
+ }
17
+ end
18
+
19
+ # … 45 other methods
20
+
21
+ private
22
+
23
+ def eligible_promotions(coupon_code)
24
+ # 30 lines of gnarly queries and conditions
25
+ end
26
+ end
27
+ ```
28
+
29
+ Suppose a developer wants to open up `user_spec.rb` and write a spec for
30
+ `best_promo`. While `best_promo` might not be the simplest method in the world,
31
+ one might imagine a straightforward test with two or three test cases based on
32
+ the existence of different `CouponCode` records.
33
+
34
+ But inevitably, during test setup, it becomes clear that the stuff going on in
35
+ `eligible_promotions` is _intense_, and in order to get the database into the
36
+ state it would need to be to exercise the logic in `best_promo`, the test would
37
+ require fifteen lines of painful and confusing setup code.
38
+
39
+ This gives rise to the idea: "I could just mock out `eligible_promotions` and
40
+ this test would be far clearer and easier to write".
41
+
42
+ Of course, if that idea led you to this page, I have bad news: Mocktail
43
+ can't help you here and it's our opinion that getting into the habit of using
44
+ mocking for this purpose is a _really_ bad idea.
45
+
46
+ Why?
47
+
48
+ First of all, to mock the `eligible_promotions` method in this case would be an
49
+ example of a [partial mock](../support/glossary.md#partial-mock) (discussed
50
+ [elsewhere](./partial_mocks.md)). Moreover, that partial mock would be the
51
+ _subject itself_—meaning, a test to validate a subject's public API would fake
52
+ out the subject's private implementation. As a result, no meaningful confidence
53
+ would really be gained from such a test. At best, a test might tell you how the
54
+ `best_promo` method would behave in the hypothetical scenario of however
55
+ `eligible_promotions` was [stubbed](../support/glossary.md#stub), but that
56
+ isn't a particularly interesting thing to know (much less codify in a test suite
57
+ for perpetuity).
58
+
59
+ Nevertheless, "mocking the subject" has been remarkably common in Rails
60
+ applications since the mid-2000s. In 2010, [DHH
61
+ tweeted](https://twitter.com/dhh/status/27444365459?s=20) something that was
62
+ seen as controversial with respect to test runners (as RSpec had gained a
63
+ massive following) but entirely mundane when it came to mocking libraries
64
+ ([mocha](https://github.com/freerange/mocha), in this case):
65
+
66
+ > Q: What testing framework do you use at 37signals? A: test/unit with the occasional splash of mocha. (That's all you need for great testing)
67
+
68
+ In the experience of the present author, that "splash of mocha" was almost
69
+ always used in cases like the above: to fake out one method in an Active Record
70
+ model in the service of testing another method in the same model. It made the
71
+ initial writing of a test easier, but at the cost of its comprehensibility and
72
+ long-term maintainability.
73
+
74
+ So, if mocking out a method on the subject isn't the solution, what should you
75
+ do instead?
76
+
77
+ In almost every case, this situation is indicating that the subject is serving
78
+ too many disparate purposes and the only workable answer is that the method
79
+ being tested, the method being mocked, or perhaps _both_ should be extracted
80
+ into classes of their own—complete with proper names and a clear
81
+ [dependency](../support/glossary.md#dependency) relationship. Only _then_ might
82
+ an isolated unit test with mocks be appropriate as a tool to help specify the
83
+ terms of that relationship.
84
+
85
+ To illustrate, we could carry through this refactor by extracting both methods
86
+ into new classes and referencing them from the original `best_promo` entrypoint
87
+ in the model:
88
+
89
+ ```ruby
90
+ class User < ApplicationRecord
91
+ def best_promo
92
+ FindsBestPromo.new.find(CouponCode.active)
93
+ end
94
+ end
95
+
96
+ class FindsBestPromo
97
+ def initialize
98
+ @determines_eligible_promotions = DeterminesEligiblePromotions.new
99
+ end
100
+
101
+ def find(coupon_codes)
102
+ coupon_codes.flat_map { |coupon_code|
103
+ @determines_eligible_promotions.determine(coupon_code)
104
+ }.max_by { |promo|
105
+ promo.percent_discount
106
+ }
107
+ end
108
+ end
109
+
110
+ class DeterminesEligiblePromotions
111
+ def determine(coupon_code)
112
+ # 30 lines, still gnarly
113
+ end
114
+ end
115
+ ```
116
+
117
+ Finally, we could write a test of `FindsBestPromo` that used Mocktail:
118
+
119
+ ```ruby
120
+ class FindsBestPromoTest < Minitest::Test
121
+ def setup
122
+ @determines_eligible_promotions = Mocktail.of_next(DeterminesEligiblePromotions)
123
+ @subject = FindsBestPromo.new
124
+ end
125
+
126
+ def test_max_discount
127
+ stubs { @determines_eligible_promotions.determine("NEAT25") }.with {
128
+ [
129
+ Promo.new(name: "A", percent_discount: 20),
130
+ Promo.new(name: "B", percent_discount: 25)
131
+ ]
132
+ }
133
+ stubs { @determines_eligible_promotions.determine("COOL15") }.with {
134
+ [Promo.new(name: "C", percent_discount: 15)]
135
+ }
136
+
137
+ result = @subject.find(["NEAT25", "COOL15"])
138
+
139
+ assert_equal "B", result.name
140
+ end
141
+ end
142
+ ```
143
+
144
+ Given the starting point, however, it's important to admit that nothing about
145
+ this is easy. Extracting behavior from a years-old class that's juggling dozens
146
+ of responsibilities where inter-dependencies abound is never as simple as
147
+ cut-and-paste. And creating new, single-use classes in a codebase dominated by a
148
+ handful of massive files will surely feel out of place and inconsistent.
149
+ Finally, deviating from the perceived "Rails Way" requires thoughtful planning,
150
+ and is likely to engender conflict without winning the buy-in of all interested
151
+ parties. That said, there's no time like the present, because this work willl
152
+ only become more difficult with time.
153
+
154
+ If you've read this far and feel a little hopeless about how to make forward
155
+ progress wrangling complexity of this scale, it may make sense to discuss
156
+ bringing in outside help, like [the kind we offer at Test
157
+ Double](https://testdouble.com/contact).
158
+
159
+ **If you've heard enough, you can go back and consider [non-TDD use cases for Mocktail](../other_uses.md).**
160
+
161
+ **Or if you're finally ready to walk the golden path, you can revisit [Mocktail as a TDD tool](../tdd.md).**
@@ -0,0 +1,34 @@
1
+ # Mocking out time
2
+
3
+ If you want to fake out time in a Ruby test, there are three things to think
4
+ about:
5
+
6
+ 1. Editing the actual system time will prevent you from running tests in
7
+ parallel, will break HTTPS/SSL certificate authentication, and will generally
8
+ wreak havoc on most modern computers
9
+
10
+ 2. Editing the value of when "now" is for `Time` and `Date` in Ruby means that
11
+ your Ruby code will have a very different time than your database, any network
12
+ dependencies, and any binaries you shell out to
13
+
14
+ 3. _Freezing_ time to a specific, fixed value can open the door to bugs due to
15
+ code that comes to depend on that fixedness. For example, two measurements of
16
+ "now" equalling one another when that can never be guaranteed to happen in
17
+ production
18
+
19
+ Where does that leave you? I don't know, but Mocktail doesn't do anything to
20
+ help you here.
21
+
22
+ The best tool for this job is definitely the [timecop
23
+ gem](https://github.com/travisjeffery/timecop). Just use its `travel` methods to
24
+ shift the Ruby-time to where you want, and be mindful that the time will
25
+ disagree with any network or system dependencies.
26
+
27
+ Oh, and if you're looking at the [ActiveSupport time
28
+ helpers](https://api.rubyonrails.org/v5.2.4/classes/ActiveSupport/Testing/TimeHelpers.html),
29
+ just know that even when they _say_ `travel`, they're actually _freezing_ time,
30
+ which creates the category of problems described in issue #3 above.
31
+
32
+ **If you've heard enough, you can go back and consider [non-TDD use cases for Mocktail](../other_uses.md).**
33
+
34
+ **Or if you're finally ready to walk the golden path, you can revisit [Mocktail as a TDD tool](../tdd.md).**
@@ -0,0 +1,34 @@
1
+ # Creating partial mocks that fake out a subset of methods
2
+
3
+ If you've looked at Mocktail's APIs for [creating mock
4
+ instances](../support/api.md#mocktailof) and [replacing class or module
5
+ methods](../support/api.md#mocktailreplace), you'll find that it's an a bit of
6
+ an _all or nothing_ affair. You can get create an instance of a class with
7
+ _all_ its methods replaced with `nil`-returning fakes and you can replace _all_
8
+ a module's module methods, but **you can't tell Mocktail to only replace one or a
9
+ handful of methods while continuing to call through to some other real methods**.
10
+
11
+ Why?! Why would we do something so unfair as to withhold such obviously useful
12
+ functionality?
13
+
14
+ Well, the answer—like everything having to do with [test
15
+ doubles](../support/glossary.md#test-double)—requires some nuance. In testing
16
+ parlance, to replace some of the methods on a
17
+ [dependency](../support/glossary.md#dependency) but not all of them is to create
18
+ what is called a [partial mock](../support/glossary.md#partial-mock) (click
19
+ through its glossary definition for some of the reasons partial mocks are
20
+ considered to be an antipattern).
21
+
22
+ Mocktail was written to promote test-driven development that specifies
23
+ thoughtfully-designed classes that interact with other classes, but partial
24
+ mocks actually detract from that purpose in practice. As a result Mocktail,
25
+ doesn't offer a way to create partial mocks.
26
+
27
+ If you find yourself wanting to reach for a partial mock, we'd encourage you to
28
+ first take it as potential design feedback that a dependency is perhaps too big
29
+ and its contract with [subject](../support/glossary.md#subject-under-test) too
30
+ porous.
31
+
32
+ **If you've heard enough, you can go back and consider [non-TDD use cases for Mocktail](../other_uses.md).**
33
+
34
+ **Or if you're finally ready to walk the golden path, you can revisit [Mocktail as a TDD tool](../tdd.md).**
@@ -0,0 +1,52 @@
1
+ # Recording interactions on real methods
2
+
3
+ Okay, so imagine this situation: you have a unit test that, among other things,
4
+ calls some API that doesn't return a meaningful
5
+ [value](../support/glossary.md#value) and you want to make sure that you've
6
+ covered it. Since your test can't observe the result of the call, you want to
7
+ make sure it happened
8
+
9
+ ```ruby
10
+ def roll_dice(dice)
11
+ @mouth.blow!(dice)
12
+ dice.roll
13
+ end
14
+ ```
15
+
16
+ If a developer were simultaneously very eager to achieve an extreme amount of
17
+ code coverage and also superstitious that _actually_ blowing on the dice is very
18
+ important, they could look at that `@mouth.blow!` invocation and ask "how can I
19
+ verify that call happened while still calling through to its real
20
+ implementation?"
21
+
22
+ Well, the answer if you're using Mocktail is, "sorry, you can't."
23
+
24
+ The primary reason this isn't supported is because Mocktail exists to facilitate
25
+ test-driven development of code that's fully isolated from its
26
+ [dependencies](../support/glossary.md#dependency) in order to design
27
+ well-considered interactions, and calling through to a dependency's actual
28
+ implementation naturally violates that isolation. Additionally, for any reason a
29
+ tester might wish to call through to the actual implementation _other than
30
+ superstition_, it stands to reason that at most one of the following is
31
+ possible:
32
+
33
+ 1. An integrated test that doesn't use mocks could indirectly observe a
34
+ dependency's side effect as evidence that a call occurred and therefore a
35
+ mocking library isn't required to [proxy it](../support/glossary.md#proxy), _or_
36
+ 2. It's not possible for the side effect of the call to be observed by either
37
+ the test or its [subject](../support/glossary.md#subject-under-test) and
38
+ therefore it's safe to achieve proper isolation by replacing the real
39
+ implementation with a fake one
40
+
41
+ But both conditions can't be true. As a result, the urge to add a test
42
+ assertion that a call occurred in a particular way may represent an [unnecessary
43
+ over-specification](https://blog.testdouble.com/posts/2020-02-25-necessary-and-sufficient/).
44
+
45
+ That said, if you're unswayed and still set on having this functionality, you
46
+ can find it in the [rr
47
+ gem](https://github.com/rr/rr/blob/master/doc/03_api_overview.md#mockproxy)'s
48
+ `mock.proxy` API.
49
+
50
+ **If you've heard enough, you can go back and consider [non-TDD use cases for Mocktail](../other_uses.md).**
51
+
52
+ **Or if you're finally ready to walk the golden path, you can revisit [Mocktail as a TDD tool](../tdd.md).**
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,99 @@
1
+ # Installing Mocktail with Sorbet
2
+
3
+ Alright, when you add a type checker to isolated unit tests, you're really
4
+ cooking with gas. That's because tests that mock out
5
+ [dependencies](support/glossary.md#dependency) to design the internal APIs
6
+ a [subject](support/glossary.md#subject-under-test) will invoke to divide
7
+ its job into smaller, focused sub-tasks is really about defining the _contracts_
8
+ between units, and those contracts are most often defined by method signatures:
9
+ parameters and return values.
10
+
11
+ And what are type checkers most concerned with? Oh yeah, parameters and return
12
+ values! Instead of potentially brittle tests that may keep passing even when the
13
+ contract changes, tests that are strictly type-checked won't lie to you.
14
+ Additionally, editors that support [red squiggles and
15
+ autocorrections](https://sorbet.org/docs/vscode) for Sorbet can enable
16
+ test-driven workflows you won't see elsewhere in Ruby: invoke a method as you
17
+ wish it existed in your test, and a quickfix to update the method's definition
18
+ is often just a keyboard shortcut away. The power you'll feel is intoxicating!
19
+
20
+ All that is why topping your Mocktail with Sorbet is a match made in zero-proof
21
+ testing heaven. All you need to do to install it is add this to your Gemfile:
22
+
23
+ ```ruby
24
+ gem "mocktail", group: :test, require: "mocktail/sorbet"
25
+ ```
26
+
27
+ Note the goofy `require: "mocktail/sorbet"` option above. We don't see that a
28
+ lot in Ruby, because most gems have a single primary entrypoint to require, and
29
+ it's almost always the same name as the gem. However, so that Mocktail can be
30
+ used with and without [Sorbet's runtime type
31
+ safety](https://sorbet.org/docs/runtime) enabled, it actually ships with two
32
+ distributions:
33
+
34
+ * `require "mocktail"` will load a version of the library that has [effectively
35
+ erased](https://github.com/kddnewton/sorbet-eraser) all of its Sorbet types, so
36
+ you'll never see a runtime type check
37
+ * `require "mocktail/sorbet"` will load the library with all of its Sorbet
38
+ signatures intact, so that Sorbet users can benefit from both static and runtime
39
+ checks
40
+
41
+ Be warned: it's all Sorbet or no Sorbet. **Do not cross the streams and require
42
+ both of them** unless you like constant redefinition warnings. Mocktail may
43
+ throw an error to try to fail fast if it detects both are required, since the
44
+ resulting behavior is undefined.
45
+
46
+ As for Sorbet, that's really all you need to know! Everything else is just the
47
+ usual stack of tools like the [srb CLI](https://sorbet.org/docs/cli),
48
+ [tapioca](https://github.com/Shopify/tapioca), and
49
+ [spoom](https://github.com/Shopify/spoom), plus whatever editor integration you
50
+ prefer.
51
+
52
+ ## Type checking your tests
53
+
54
+ If you want to benefit from type checking in your tests, it means you'll have to
55
+ enable type checking in your tests. Code examples you find in the rest of
56
+ Mocktail's documentation won't show this, but a test listing might look like
57
+ this to get all the benefits that Sorbet-flavored mocktail has to offer
58
+ (Minitest below):
59
+
60
+ ```ruby
61
+ # typed: strict
62
+
63
+ require "test_helper"
64
+
65
+ class StuffDoerTest < Minitest::Test
66
+ extend T::Sig
67
+
68
+ sig { params(name: String).void }
69
+ def initialize(name)
70
+ super
71
+ @loads_stuff = T.let(Mocktail.of_next(LoadsStuff), LoadsStuff)
72
+
73
+ @subject = T.let(StuffDoer.new, StuffDoer)
74
+ end
75
+
76
+ sig { void }
77
+ def test_doing_stuff
78
+ # tests go here
79
+ end
80
+ end
81
+ ```
82
+
83
+ It probably won't be a surprise to have to add `sig` to each test case when
84
+ using `typed: strict`, but the `T.let` in the initializer is a definite bummer.
85
+ This could probably be worked around with memoized helpers, but that's outside
86
+ the scope of Mocktail for now, especially considering [Sorbet may obviate the
87
+ need for explicit declaration of certain instance variables in the
88
+ future](https://github.com/sorbet/sorbet/issues/3513).
89
+
90
+ ## Configuring your test runner
91
+
92
+ But before we dive in and start writing tests, we need to configure Mocktail
93
+ with your preferred test runner.
94
+
95
+ **If you were glad to see the code example above was in Minitest, [keep the party going](configuring_minitest.md).**
96
+
97
+ **If you're annoyed it wasn't in RSpec, [switch trains to go to RSpec-land](configuring_rspec.md).**
98
+
99
+
@@ -0,0 +1,29 @@
1
+ # Installing Mocktail
2
+
3
+ So, you want to get started with Mocktail Classic™, do you? Here's how to get it
4
+ installed! (And if you're having second thoughts on passing on [the Sorbet
5
+ edition](installation_sorbet.md), there's still time!)
6
+
7
+ ## Installation
8
+
9
+ First thing's first, add this to your Gemfile:
10
+
11
+ ```ruby
12
+ gem "mocktail", group: :test, require: "mocktail"
13
+ ```
14
+
15
+ (That redundant `require` option is just a garnish and won't cause any harm;
16
+ it's there to signal to future readers that you're _not_ using
17
+ `mocktail/sorbet`.)
18
+
19
+ Once installed, you can require Mocktail like you might expect:
20
+
21
+ ```ruby
22
+ require "mocktail"
23
+ ```
24
+
25
+ Next step: it's time to configure Mocktail with your test runner!
26
+
27
+ **If you like to keep things classy, maybe [you use Minitest](configuring_minitest.md).**
28
+
29
+ **Or maybe you'd rather express your intent [to use RSpec](configuring_rspec.md).**
@@ -0,0 +1,32 @@
1
+ # Other uses for Mocktail
2
+
3
+ If you're looking to accomplish something with Mocktail that doesn't involve
4
+ test-driven development, let's get real. There's a very high likelihood that
5
+ what you're looking to do is either:
6
+
7
+ 1. Not the best tool for the job
8
+ 2. Not a job worth doing
9
+
10
+ But hey, could be wrong. The present author once did a [talk about all the ways
11
+ people abuse mocking
12
+ libraries](https://blog.testdouble.com/talks/2018-03-06-please-dont-mock-me/)
13
+ and undermine the value of their tests, so there is a track record of bias here.
14
+
15
+ By popular demand, here are some ways you might be thinking about using
16
+ Mocktail:
17
+
18
+ **Mocking out the system clock in a vain attempt to [master space and time](faq/mocking_time.md).**
19
+
20
+ **Mocking an HTTP API by faking out [Ruby's built-in networking](faq/mocking_http.md).**
21
+
22
+ **Mocking out _just one_ method on an [otherwise real object](faq/partial_mocks.md).**
23
+
24
+ **Recording method invocations while [calling through to their real implementation](faq/verifying_real_interactions.md).**
25
+
26
+ **Using Mocktail to fix an existing test that's [failing in a gnarly way you don't understand](faq/existing_tests.md).**
27
+
28
+ **Mocking out a method on the subject under test, AKA [the thing you're testing itself](faq/mocking_the_subject.md).**
29
+
30
+ ---
31
+
32
+ **Once you've seen enough, you can take a second look at [Mocktail as a TDD tool](tdd.md).**