mocktail 2.0.0 → 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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +2 -2
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +3 -2
  5. data/Gemfile.lock +76 -82
  6. data/README.md +2 -0
  7. data/docs/code_of_conduct.md +8 -0
  8. data/docs/configuring_minitest.md +20 -0
  9. data/docs/configuring_rspec.md +19 -0
  10. data/docs/example_test.md +594 -0
  11. data/docs/faq/existing_tests.md +47 -0
  12. data/docs/faq/mocking_http.md +151 -0
  13. data/docs/faq/mocking_the_subject.md +161 -0
  14. data/docs/faq/mocking_time.md +34 -0
  15. data/docs/faq/partial_mocks.md +34 -0
  16. data/docs/faq/verifying_real_interactions.md +52 -0
  17. data/docs/img/delegator_tree.png +0 -0
  18. data/docs/img/example_test.png +0 -0
  19. data/docs/img/extract_transform_load.png +0 -0
  20. data/docs/img/mocktail_sorbet.jpg +0 -0
  21. data/docs/img/mocktail_untyped.jpg +0 -0
  22. data/docs/img/spacer.png +0 -0
  23. data/docs/installation_sorbet.md +99 -0
  24. data/docs/installation_untyped.md +29 -0
  25. data/docs/other_uses.md +32 -0
  26. data/docs/stubbing_and_verifying.md +515 -0
  27. data/docs/support/api.md +819 -0
  28. data/docs/support/example_test.rb +94 -0
  29. data/docs/support/glossary.md +426 -0
  30. data/docs/tdd/class_methods.md +44 -0
  31. data/docs/tdd/poro/dependency_inception.md +136 -0
  32. data/docs/tdd/poro/dependency_injection.md +82 -0
  33. data/docs/tdd/poro.md +15 -0
  34. data/docs/tdd/third_party.md +220 -0
  35. data/docs/tdd.md +15 -0
  36. data/lib/mocktail/debug.rb +4 -3
  37. data/lib/mocktail/share/stringifies_call.rb +0 -1
  38. data/lib/mocktail/simulates_argument_error/transforms_params.rb +2 -2
  39. data/lib/mocktail/sorbet/mocktail/debug.rb +4 -3
  40. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +0 -1
  41. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +2 -2
  42. data/lib/mocktail/sorbet/mocktail/value/call.rb +1 -1
  43. data/lib/mocktail/sorbet/mocktail/version.rb +1 -1
  44. data/lib/mocktail/value/call.rb +1 -1
  45. data/lib/mocktail/version.rb +1 -1
  46. data/mocktail.gemspec +2 -2
  47. data/rbi/mocktail-pregenerated.rbi +58 -0
  48. data/script/build +47 -0
  49. data/script/setup +17 -0
  50. data/script/spoom_me +7 -0
  51. data/script/strip_sigils +17 -0
  52. data/script/test +33 -0
  53. data/script/test_double_require_warnings +46 -0
  54. data/script/update +21 -0
  55. data/sorbet/config +12 -0
  56. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  57. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  58. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  59. data/sorbet/rbi/gems/docile@1.4.0.rbi +376 -0
  60. data/sorbet/rbi/gems/json@2.6.3.rbi +1533 -0
  61. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  62. data/sorbet/rbi/gems/lint_roller@1.0.0.rbi +230 -0
  63. data/sorbet/rbi/gems/m@1.6.1.rbi +257 -0
  64. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  65. data/sorbet/rbi/gems/minitest@5.18.0.rbi +1491 -0
  66. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  67. data/sorbet/rbi/gems/parallel@1.23.0.rbi +273 -0
  68. data/sorbet/rbi/gems/parser@3.2.2.1.rbi +7253 -0
  69. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  70. data/sorbet/rbi/gems/rake@13.0.6.rbi +3024 -0
  71. data/sorbet/rbi/gems/rbi@0.0.16.rbi +3049 -0
  72. data/sorbet/rbi/gems/regexp_parser@2.8.0.rbi +3748 -0
  73. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
  74. data/sorbet/rbi/gems/rubocop-ast@1.29.0.rbi +7003 -0
  75. data/sorbet/rbi/gems/rubocop-performance@1.18.0.rbi +8 -0
  76. data/sorbet/rbi/gems/rubocop-sorbet@0.7.0.rbi +1043 -0
  77. data/sorbet/rbi/gems/rubocop@1.52.0.rbi +55500 -0
  78. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  79. data/sorbet/rbi/gems/simplecov-html@0.12.3.rbi +216 -0
  80. data/sorbet/rbi/gems/simplecov@0.22.0.rbi +2148 -0
  81. data/sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi +8 -0
  82. data/sorbet/rbi/gems/spoom@1.2.1.rbi +2536 -0
  83. data/sorbet/rbi/gems/standard-custom@1.0.1.rbi +63 -0
  84. data/sorbet/rbi/gems/standard-performance@1.1.0.rbi +8 -0
  85. data/sorbet/rbi/gems/standard@1.29.0.rbi +850 -0
  86. data/sorbet/rbi/gems/tapioca@0.11.6.rbi +3301 -0
  87. data/sorbet/rbi/gems/thor@1.2.2.rbi +3965 -0
  88. data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
  89. data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
  90. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  91. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  92. data/sorbet/tapioca/config.yml +13 -0
  93. data/sorbet/tapioca/require.rb +4 -0
  94. data/spoom_data/05f0c49.json +1 -0
  95. data/spoom_data/1657012.json +1 -0
  96. data/spoom_data/167434b.json +1 -0
  97. data/spoom_data/1d0ba5b.json +1 -0
  98. data/spoom_data/1ffa724.json +1 -0
  99. data/spoom_data/2129b3d.json +1 -0
  100. data/spoom_data/24b1c92.json +1 -0
  101. data/spoom_data/2c46aee.json +1 -0
  102. data/spoom_data/305ec0b.json +1 -0
  103. data/spoom_data/30e9528.json +1 -0
  104. data/spoom_data/4638cd5.json +1 -0
  105. data/spoom_data/47c7dad.json +1 -0
  106. data/spoom_data/4b1edef.json +1 -0
  107. data/spoom_data/4de157f.json +1 -0
  108. data/spoom_data/526e7db.json +1 -0
  109. data/spoom_data/5d093b9.json +1 -0
  110. data/spoom_data/5db3b43.json +1 -0
  111. data/spoom_data/5fe2a65.json +1 -0
  112. data/spoom_data/6891312.json +1 -0
  113. data/spoom_data/6b0fef4.json +1 -0
  114. data/spoom_data/6b83d12.json +1 -0
  115. data/spoom_data/74c83c2.json +1 -0
  116. data/spoom_data/7644ff4.json +1 -0
  117. data/spoom_data/79054db.json +1 -0
  118. data/spoom_data/814e515.json +1 -0
  119. data/spoom_data/88c3b60.json +1 -0
  120. data/spoom_data/8bd4b6e.json +1 -0
  121. data/spoom_data/93f8153.json +1 -0
  122. data/spoom_data/95242fe.json +1 -0
  123. data/spoom_data/97f4c09.json +1 -0
  124. data/spoom_data/a13d150.json +1 -0
  125. data/spoom_data/a17f215.json +1 -0
  126. data/spoom_data/b705a9d.json +1 -0
  127. data/spoom_data/ba19195.json +1 -0
  128. data/spoom_data/bef51ca.json +1 -0
  129. data/spoom_data/dbb595a.json +1 -0
  130. data/spoom_data/e8fab92.json +1 -0
  131. data/spoom_data/f166c87.json +1 -0
  132. data/spoom_data/f57992d.json +1 -0
  133. data/spoom_data/f5a1e40.json +1 -0
  134. data/spoom_data/f6d6431.json +1 -0
  135. data/spoom_data/f72b67c.json +1 -0
  136. data/spoom_data/fc2f231.json +1 -0
  137. data/src/mocktail/collects_calls.rb +18 -0
  138. data/src/mocktail/debug.rb +55 -0
  139. data/src/mocktail/dsl.rb +46 -0
  140. data/src/mocktail/errors.rb +19 -0
  141. data/src/mocktail/explains_nils.rb +41 -0
  142. data/src/mocktail/explains_thing.rb +137 -0
  143. data/src/mocktail/grabs_original_method_parameters.rb +33 -0
  144. data/src/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  145. data/src/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  146. data/src/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  147. data/src/mocktail/handles_dry_call/logs_call.rb +12 -0
  148. data/src/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  149. data/src/mocktail/handles_dry_call.rb +25 -0
  150. data/src/mocktail/handles_dry_new_call.rb +42 -0
  151. data/src/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  152. data/src/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  153. data/src/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  154. data/src/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  155. data/src/mocktail/imitates_type/makes_double.rb +29 -0
  156. data/src/mocktail/imitates_type.rb +29 -0
  157. data/src/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  158. data/src/mocktail/initializes_mocktail.rb +25 -0
  159. data/src/mocktail/matcher_presentation.rb +21 -0
  160. data/src/mocktail/matchers/any.rb +27 -0
  161. data/src/mocktail/matchers/base.rb +39 -0
  162. data/src/mocktail/matchers/captor.rb +76 -0
  163. data/src/mocktail/matchers/includes.rb +32 -0
  164. data/src/mocktail/matchers/includes_hash.rb +12 -0
  165. data/src/mocktail/matchers/includes_key.rb +12 -0
  166. data/src/mocktail/matchers/includes_string.rb +12 -0
  167. data/src/mocktail/matchers/is_a.rb +17 -0
  168. data/src/mocktail/matchers/matches.rb +19 -0
  169. data/src/mocktail/matchers/not.rb +17 -0
  170. data/src/mocktail/matchers/numeric.rb +27 -0
  171. data/src/mocktail/matchers/that.rb +32 -0
  172. data/src/mocktail/matchers.rb +19 -0
  173. data/src/mocktail/raises_neato_no_method_error.rb +93 -0
  174. data/src/mocktail/records_demonstration.rb +43 -0
  175. data/src/mocktail/registers_matcher.rb +65 -0
  176. data/src/mocktail/registers_stubbing.rb +31 -0
  177. data/src/mocktail/replaces_next.rb +55 -0
  178. data/src/mocktail/replaces_type/redefines_new.rb +32 -0
  179. data/src/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  180. data/src/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  181. data/src/mocktail/replaces_type.rb +36 -0
  182. data/src/mocktail/resets_state.rb +14 -0
  183. data/src/mocktail/share/bind.rb +18 -0
  184. data/src/mocktail/share/cleans_backtrace.rb +22 -0
  185. data/src/mocktail/share/creates_identifier.rb +39 -0
  186. data/src/mocktail/share/determines_matching_calls.rb +72 -0
  187. data/src/mocktail/share/stringifies_call.rb +84 -0
  188. data/src/mocktail/share/stringifies_method_name.rb +16 -0
  189. data/src/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  190. data/src/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  191. data/src/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  192. data/src/mocktail/simulates_argument_error.rb +36 -0
  193. data/src/mocktail/sorbet.rb +3 -0
  194. data/src/mocktail/stringifies_method_signature.rb +53 -0
  195. data/src/mocktail/typed.rb +5 -0
  196. data/src/mocktail/value/cabinet.rb +91 -0
  197. data/src/mocktail/value/call.rb +51 -0
  198. data/src/mocktail/value/demo_config.rb +10 -0
  199. data/src/mocktail/value/double.rb +10 -0
  200. data/src/mocktail/value/double_data.rb +15 -0
  201. data/src/mocktail/value/explanation.rb +68 -0
  202. data/src/mocktail/value/explanation_data.rb +19 -0
  203. data/src/mocktail/value/fake_method_data.rb +11 -0
  204. data/src/mocktail/value/matcher_registry.rb +27 -0
  205. data/src/mocktail/value/no_explanation_data.rb +20 -0
  206. data/src/mocktail/value/signature.rb +35 -0
  207. data/src/mocktail/value/stubbing.rb +26 -0
  208. data/src/mocktail/value/top_shelf.rb +79 -0
  209. data/src/mocktail/value/type_replacement.rb +11 -0
  210. data/src/mocktail/value/type_replacement_data.rb +19 -0
  211. data/src/mocktail/value/unsatisfying_call.rb +9 -0
  212. data/src/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  213. data/src/mocktail/value.rb +19 -0
  214. data/src/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  215. data/src/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  216. data/src/mocktail/verifies_call/raises_verification_error.rb +74 -0
  217. data/src/mocktail/verifies_call.rb +37 -0
  218. data/src/mocktail/version.rb +12 -0
  219. data/src/mocktail.rb +154 -0
  220. data/sub_projects/rbi_generator/Gemfile +4 -0
  221. data/sub_projects/rbi_generator/Gemfile.lock +63 -0
  222. data/sub_projects/rbi_generator/sorbet/rbi/gems/.gitattributes +1 -0
  223. data/sub_projects/sorbet_user/Gemfile +13 -0
  224. data/sub_projects/sorbet_user/Gemfile.lock +126 -0
  225. data/sub_projects/sorbet_user/Rakefile +10 -0
  226. data/sub_projects/sorbet_user/bin/tapioca +27 -0
  227. data/sub_projects/sorbet_user/rbi/mocktail.rbi +9 -0
  228. data/sub_projects/sorbet_user/script/test +13 -0
  229. data/sub_projects/sorbet_user/sorbet/config +4 -0
  230. data/sub_projects/sorbet_user/sorbet/rbi/gems/.gitattributes +1 -0
  231. data/sub_projects/sorbet_user/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
  232. data/sub_projects/sorbet_user/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  233. data/sub_projects/sorbet_user/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
  234. data/sub_projects/sorbet_user/sorbet/rbi/gems/json@2.21.2.rbi +2140 -0
  235. data/sub_projects/sorbet_user/sorbet/rbi/gems/language_server-protocol@3.17.0.4.rbi +14244 -0
  236. data/sub_projects/sorbet_user/sorbet/rbi/gems/lint_roller@1.1.0.rbi +239 -0
  237. data/sub_projects/sorbet_user/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
  238. data/sub_projects/sorbet_user/sorbet/rbi/gems/m@1.6.2.rbi +258 -0
  239. data/sub_projects/sorbet_user/sorbet/rbi/gems/method_source@1.1.0.rbi +9 -0
  240. data/sub_projects/sorbet_user/sorbet/rbi/gems/minitest@5.27.0.rbi +1549 -0
  241. data/sub_projects/sorbet_user/sorbet/rbi/gems/mocktail@3.0.0.rbi +1923 -0
  242. data/sub_projects/sorbet_user/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  243. data/sub_projects/sorbet_user/sorbet/rbi/gems/parallel@1.26.3.rbi +291 -0
  244. data/sub_projects/sorbet_user/sorbet/rbi/gems/parser@3.3.7.4.rbi +5451 -0
  245. data/sub_projects/sorbet_user/sorbet/rbi/gems/prism@1.4.0.rbi +41732 -0
  246. data/sub_projects/sorbet_user/sorbet/rbi/gems/racc@1.8.1.rbi +164 -0
  247. data/sub_projects/sorbet_user/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  248. data/sub_projects/sorbet_user/sorbet/rbi/gems/rake@13.2.1.rbi +3028 -0
  249. data/sub_projects/sorbet_user/sorbet/rbi/gems/rbi@0.3.1.rbi +6599 -0
  250. data/sub_projects/sorbet_user/sorbet/rbi/gems/rbs@3.9.2.rbi +6976 -0
  251. data/sub_projects/sorbet_user/sorbet/rbi/gems/regexp_parser@2.10.0.rbi +3795 -0
  252. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-ast@1.43.0.rbi +7717 -0
  253. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-performance@1.24.0.rbi +9 -0
  254. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop-sorbet@0.9.0.rbi +9 -0
  255. data/sub_projects/sorbet_user/sorbet/rbi/gems/rubocop@1.73.2.rbi +61306 -0
  256. data/sub_projects/sorbet_user/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  257. data/sub_projects/sorbet_user/sorbet/rbi/gems/sorbet-eraser@0.3.1.rbi +8 -0
  258. data/sub_projects/sorbet_user/sorbet/rbi/gems/spoom@1.6.1.rbi +7274 -0
  259. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-custom@1.0.2.rbi +8 -0
  260. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-performance@1.7.0.rbi +9 -0
  261. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard-sorbet@0.0.3.rbi +53 -0
  262. data/sub_projects/sorbet_user/sorbet/rbi/gems/standard@1.47.0.rbi +935 -0
  263. data/sub_projects/sorbet_user/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
  264. data/sub_projects/sorbet_user/sorbet/rbi/gems/thor@1.3.2.rbi +4378 -0
  265. data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
  266. data/sub_projects/sorbet_user/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +254 -0
  267. data/sub_projects/sorbet_user/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
  268. data/sub_projects/sorbet_user/sorbet/rbi/gems/yard@0.9.45.rbi +18916 -0
  269. data/sub_projects/sorbet_user/sorbet/tapioca/config.yml +13 -0
  270. data/sub_projects/sorbet_user/sorbet/tapioca/require.rb +4 -0
  271. data/sub_projects/sorbet_user/test/ensure_type_safety_test.rb +21 -0
  272. data/sub_projects/sorbet_user/test/paint_by_number_test.rb +113 -0
  273. data/sub_projects/sorbet_user/test/sorbet_test.rb +289 -0
  274. data/sub_projects/sorbet_user/test/test_helper.rb +35 -0
  275. data/sub_projects/untyped_user/Gemfile +4 -0
  276. data/sub_projects/untyped_user/Gemfile.lock +25 -0
  277. data/sub_projects/untyped_user/antitype_test.rb +54 -0
  278. data/sub_projects/untyped_user/script/test +5 -0
  279. metadata +264 -8
@@ -0,0 +1,13 @@
1
+ gem:
2
+ # Add your `gem` command parameters here:
3
+ #
4
+ # exclude:
5
+ # - gem_name
6
+ # doc: true
7
+ # workers: 5
8
+ dsl:
9
+ # Add your `dsl` command parameters here:
10
+ #
11
+ # exclude:
12
+ # - SomeGeneratorName
13
+ # workers: 5
@@ -0,0 +1,4 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ # Add your extra requires here (`bin/tapioca require` can be used to bootstrap this list)
@@ -0,0 +1,21 @@
1
+ # typed: false
2
+
3
+ require "test_helper"
4
+
5
+ class EnsureTypeSafetyTest < Minitest::Test
6
+ def test_basic_case
7
+ msg = assert_type_failure "\"foo\" + 1"
8
+
9
+ assert_includes msg, "Expected `String` but found `Integer(1)`"
10
+ end
11
+
12
+ def test_strict_case
13
+ msg = assert_strict_type_failure <<~RUBY
14
+ def foo
15
+ "no sig"
16
+ end
17
+ RUBY
18
+
19
+ assert_includes msg, "The method `foo` does not have a `sig`"
20
+ end
21
+ end
@@ -0,0 +1,113 @@
1
+ # typed: strict
2
+
3
+ require "test_helper"
4
+
5
+ class Region < T::Struct
6
+ const :number, Integer
7
+ const :color, T.nilable(Symbol)
8
+ end
9
+
10
+ class Page < T::Struct
11
+ const :regions, T::Array[Region]
12
+ end
13
+
14
+ class BlankBook < T::Struct
15
+ const :pages, T::Array[Page], default: []
16
+ const :color_map, T::Hash[Integer, Symbol], default: {}
17
+ end
18
+
19
+ class PaintedBook < T::Struct
20
+ const :pages, T::Array[Page], default: []
21
+ const :author, String
22
+ end
23
+
24
+ class AssemblesBook
25
+ extend T::Sig
26
+
27
+ sig { params(blank_book: BlankBook, author: String, painted_regions: T::Array[Region]).returns(PaintedBook) }
28
+ def assemble(blank_book, author:, painted_regions:)
29
+ PaintedBook.new(pages: [], author: author)
30
+ end
31
+ end
32
+
33
+ class MatchesPaint
34
+ extend T::Sig
35
+
36
+ sig { params(number: Integer).returns(Symbol) }
37
+ def match(number)
38
+ :a_color
39
+ end
40
+ end
41
+
42
+ class PaintsRegion
43
+ extend T::Sig
44
+
45
+ sig { params(region: Region, color: Symbol).returns(Region) }
46
+ def paint(region, color)
47
+ Region.new(number: region.number, color: color)
48
+ end
49
+ end
50
+
51
+ class PaintsByNumber
52
+ extend T::Sig
53
+
54
+ sig { void }
55
+ def initialize
56
+ @matches_paint = T.let(MatchesPaint.new, MatchesPaint)
57
+ @paints_region = T.let(PaintsRegion.new, PaintsRegion)
58
+ @assembles_book = T.let(AssemblesBook.new, AssemblesBook)
59
+ end
60
+
61
+ sig { params(blank_book: BlankBook, author: String).returns(PaintedBook) }
62
+ def paint(blank_book, author:)
63
+ regions = blank_book.pages.flat_map { |page| page.regions }
64
+
65
+ colors = regions.map(&:number).uniq.map { |number|
66
+ [number, @matches_paint.match(number)]
67
+ }.to_h
68
+
69
+ painted_regions = regions.map { |region| @paints_region.paint(region, colors.fetch(region.number)) }
70
+
71
+ @assembles_book.assemble(blank_book, painted_regions: painted_regions, author: author)
72
+ end
73
+ end
74
+
75
+ class PaintByNumberTest < Minitest::Test
76
+ extend T::Sig
77
+
78
+ sig { params(name: String).void }
79
+ def initialize(name)
80
+ super
81
+ @matches_paint = T.let(Mocktail.of_next(MatchesPaint), MatchesPaint)
82
+ @paints_region = T.let(Mocktail.of_next(PaintsRegion), PaintsRegion)
83
+ @assembles_book = T.let(Mocktail.of_next(AssemblesBook), AssemblesBook)
84
+
85
+ @subject = T.let(PaintsByNumber.new, PaintsByNumber)
86
+ end
87
+
88
+ sig { void }
89
+ def test_paints_by_number
90
+ blank_book = BlankBook.new(pages: [
91
+ Page.new(regions: [
92
+ region_1 = Region.new(number: 1),
93
+ region_2 = Region.new(number: 2)
94
+ ]),
95
+ Page.new(regions: [region_3 = Region.new(number: 3)])
96
+ ])
97
+ region_1_painted = Region.new(number: 1)
98
+ region_2_painted = Region.new(number: 2)
99
+ region_3_painted = Region.new(number: 3)
100
+ stubs { @matches_paint.match(1) }.with { :orange }
101
+ stubs { @matches_paint.match(2) }.with { :green }
102
+ stubs { @matches_paint.match(3) }.with { :blue }
103
+ stubs { @paints_region.paint(region_1, :orange) }.with { region_1_painted }
104
+ stubs { @paints_region.paint(region_2, :green) }.with { region_2_painted }
105
+ stubs { @paints_region.paint(region_3, :blue) }.with { region_3_painted }
106
+ expected_painted_book = PaintedBook.new(pages: [], author: "Doesn't matter")
107
+ stubs { @assembles_book.assemble(blank_book, author: "Juice", painted_regions: [region_1_painted, region_2_painted, region_3_painted]) }.with { expected_painted_book }
108
+
109
+ result = @subject.paint(blank_book, author: "Juice")
110
+
111
+ assert_equal result, expected_painted_book
112
+ end
113
+ end
@@ -0,0 +1,289 @@
1
+ # typed: strict
2
+
3
+ require "test_helper"
4
+
5
+ class SherbetTest < Minitest::Test
6
+ class Sherbet
7
+ extend T::Sig
8
+
9
+ sig { returns(Symbol) }
10
+ attr_reader :flavor
11
+
12
+ sig { params(size: T.nilable(Integer), sound: T.untyped).returns(Symbol) }
13
+ def lick(size: nil, sound: nil)
14
+ return :none if size.nil?
15
+ puts "Stop making this sound #{sound.inspect}"
16
+
17
+ if size > 10
18
+ :big
19
+ elsif size > 5
20
+ :medium
21
+ else
22
+ :small
23
+ end
24
+ end
25
+
26
+ sig { void }
27
+ def initialize
28
+ @flavor = T.let(:orange, Symbol)
29
+ end
30
+ end
31
+
32
+ include Mocktail::DSL
33
+ extend T::Sig
34
+
35
+ sig { void }
36
+ def test_stubbing
37
+ sherbet = Mocktail.of(Sherbet)
38
+ T.assert_type!(sherbet, Sherbet)
39
+
40
+ stubs { sherbet.flavor }.with { :strawberry }
41
+
42
+ assert_equal :strawberry, sherbet.flavor
43
+ T.assert_type!(sherbet.flavor, Symbol)
44
+ end
45
+
46
+ # This test makes sure the returns(T.anything) in
47
+ # lib/mocktail/handles_dry_call.rb doesn't cause issues for the test-scoped code
48
+ sig { void }
49
+ def test_stubbing_that_invokes_methods_on_return_value
50
+ sherbet = Mocktail.of(Sherbet)
51
+ T.assert_type!(sherbet, Sherbet)
52
+
53
+ stubs { sherbet.flavor }.with { :gooberry }
54
+
55
+ assert_equal :gooberry.to_s.to_sym, sherbet.flavor
56
+ T.assert_type!(sherbet.flavor, Symbol)
57
+ end
58
+
59
+ sig { void }
60
+ def test_stubbing_with_all_dem_options
61
+ sherbet = Mocktail.of(Sherbet)
62
+ T.assert_type!(sherbet, Sherbet)
63
+
64
+ Mocktail.stubs(
65
+ ignore_block: true,
66
+ ignore_extra_args: true,
67
+ ignore_arity: false,
68
+ times: 4
69
+ ) { sherbet.flavor }.with { :strawberry }
70
+
71
+ assert_equal :strawberry, sherbet.flavor
72
+ T.assert_type!(sherbet.flavor, Symbol)
73
+ end
74
+
75
+ sig { void }
76
+ def test_stubbing_with_matchers
77
+ sherbet = Mocktail.of(Sherbet)
78
+ T.assert_type!(sherbet, Sherbet)
79
+
80
+ stubs { |m|
81
+ T.assert_type!(m, Mocktail::MatcherPresentation)
82
+ sherbet.lick(size: m.is_a(Integer))
83
+ }.with { :tiny }
84
+ stubs { |m| sherbet.lick(size: 2, sound: m.any) }.with { :skosh }
85
+
86
+ T.assert_type!(sherbet.lick(size: 5), Symbol)
87
+ assert_equal :tiny, sherbet.lick(size: 1)
88
+ assert_nil sherbet.lick(size: T.unsafe(nil))
89
+
90
+ assert_equal :skosh, sherbet.lick(size: 2, sound: "yum")
91
+ assert_equal :skosh, sherbet.lick(size: 2, sound: nil)
92
+ end
93
+
94
+ class MatcherThing
95
+ extend T::Sig
96
+
97
+ sig { params(array: T::Array[Symbol]).void }
98
+ def takes_array(array)
99
+ end
100
+
101
+ sig { params(hash: T::Hash[Symbol, Integer]).void }
102
+ def takes_hash(hash)
103
+ end
104
+
105
+ sig { params(string: String).void }
106
+ def takes_string(string)
107
+ end
108
+
109
+ sig { params(integer: Integer).void }
110
+ def takes_integer(integer)
111
+ end
112
+ end
113
+
114
+ sig { void }
115
+ def test_matchers_more_precisely
116
+ thing = Mocktail.of(MatcherThing)
117
+
118
+ stubs { |m| thing.takes_array(m.includes(:ham)) }.with { nil }
119
+ stubs { |m| thing.takes_array(m.includes(:ham, :cheese)) }.with { nil }
120
+
121
+ stubs { |m| thing.takes_hash(m.includes_key(:ham)) }.with { nil }
122
+ stubs { |m| thing.takes_hash(m.includes_hash({cheese: 1}, {queso: 2})) }.with { nil }
123
+
124
+ stubs { |m| thing.takes_string(m.includes_string("s")) }.with { nil }
125
+ stubs { |m| thing.takes_string(m.includes_string("s", "t")) }.with { nil }
126
+
127
+ stubs { |m| thing.takes_string(m.matches("s")) }.with { nil }
128
+ stubs { |m| thing.takes_string(m.matches(/s/)) }.with { nil }
129
+
130
+ stubs { |m| thing.takes_integer(m.numeric) }.with { nil }
131
+
132
+ stubs { |m| thing.takes_integer(m.that { |i| i < 3 }) }.with { nil }
133
+
134
+ stubs { |m| thing.takes_integer(m.not(3)) }.with { nil }
135
+ end
136
+
137
+ class Is5Matcher < Mocktail::Matchers::Base
138
+ extend T::Sig
139
+
140
+ sig { returns(Symbol) }
141
+ def self.matcher_name
142
+ :is_5
143
+ end
144
+
145
+ sig { void }
146
+ def initialize
147
+ # Empty initialize is necessary b/c Base default expects an argument
148
+ end
149
+
150
+ sig { params(actual: T.untyped).returns(T::Boolean) }
151
+ def match?(actual)
152
+ actual == 5
153
+ end
154
+
155
+ sig { returns(String) }
156
+ def inspect
157
+ "is_5"
158
+ end
159
+ end
160
+ Mocktail.register_matcher(Is5Matcher)
161
+
162
+ sig { void }
163
+ def test_custom_matchers
164
+ thing = Mocktail.of(MatcherThing)
165
+
166
+ stubs { |m| thing.takes_integer(m.is_5) }.with { 8 }
167
+
168
+ assert_equal 8, thing.takes_integer(5)
169
+ assert_nil thing.takes_integer(9)
170
+ end
171
+
172
+ class Wastebin
173
+ extend T::Sig
174
+
175
+ sig {
176
+ params(thing: Sherbet).void
177
+ }
178
+ def dump(thing)
179
+ end
180
+ end
181
+ sig { void }
182
+ def test_verify
183
+ wastebin = Mocktail.of(Wastebin)
184
+ sherbet = Mocktail.of(Sherbet)
185
+
186
+ wastebin.dump(sherbet)
187
+
188
+ verify { wastebin.dump(sherbet) }
189
+ verify(
190
+ ignore_block: true,
191
+ ignore_extra_args: true,
192
+ ignore_arity: false,
193
+ times: 1
194
+ ) { wastebin.dump(sherbet) }
195
+ verify(
196
+ ignore_extra_args: true,
197
+ ignore_arity: true
198
+ ) { T.unsafe(wastebin).dump }
199
+ end
200
+
201
+ sig { void }
202
+ def test_captors
203
+ wastebin = Mocktail.of(Wastebin)
204
+ sherbet = Sherbet.new
205
+ captor = Mocktail.captor
206
+ T.assert_type!(captor, Mocktail::Matchers::Captor)
207
+
208
+ wastebin.dump(sherbet)
209
+
210
+ verify { wastebin.dump(captor.capture) }
211
+ assert_equal sherbet, captor.value
212
+ end
213
+
214
+ sig { void }
215
+ def test_of_next
216
+ sherbet = Mocktail.of_next(Sherbet, count: 1)
217
+ T.assert_type!(sherbet, Sherbet)
218
+
219
+ assert_equal sherbet, Sherbet.new
220
+ end
221
+
222
+ sig { void }
223
+ def test_of_next_with_count
224
+ sherbet = Mocktail.of_next_with_count(Sherbet, 2)
225
+ T.assert_type!(sherbet, T::Array[Sherbet])
226
+ end
227
+
228
+ sig { void }
229
+ def test_alias_of_next_with_count
230
+ sherbets = Mocktail.of_next_with_count(Sherbet, 2)
231
+
232
+ assert_equal 2, sherbets.size
233
+ end
234
+
235
+ module Button
236
+ extend T::Sig
237
+
238
+ sig { params(pressure: Integer, finger: T.any(String, Symbol)).void }
239
+ def self.push(pressure, finger:)
240
+ end
241
+ end
242
+
243
+ sig { void }
244
+ def test_replace
245
+ Mocktail.replace(Button)
246
+
247
+ Button.push(1, finger: :thumb)
248
+ Button.push(2, finger: :pinky)
249
+ Button.push(1, finger: "index finger")
250
+ assert_match(/redefines_singleton_methods/, Button.method(:push).source_location.first)
251
+
252
+ verify(times: 1) { |m| Button.push(1, finger: m.is_a(Symbol)) }
253
+ verify { |m| Button.push(1, finger: m.includes_string("index")) }
254
+ end
255
+
256
+ sig { void }
257
+ def test_explain
258
+ sherbet = Mocktail.of(Sherbet)
259
+
260
+ sherbet.flavor
261
+
262
+ explanation = Mocktail.explain(sherbet)
263
+
264
+ ref = explanation.reference
265
+ if !ref.is_a?(Sherbet)
266
+ assert_kind_of Mocktail::DoubleData, ref
267
+ T.assert_type!(ref.calls, T::Array[Mocktail::Call])
268
+ T.assert_type!(ref.stubbings, T::Array[Mocktail::Stubbing[T.untyped]])
269
+ end
270
+
271
+ explanation_2 = Mocktail.explain(sherbet.method(:flavor))
272
+ ref_2 = explanation_2.reference
273
+ if !ref_2.is_a?(Method)
274
+ assert_kind_of Mocktail::FakeMethodData, ref_2
275
+ T.assert_type!(ref_2.calls, T::Array[Mocktail::Call])
276
+ T.assert_type!(ref.stubbings, T::Array[Mocktail::Stubbing[T.untyped]])
277
+ end
278
+
279
+ Mocktail.replace(Button)
280
+
281
+ explanation_3 = Mocktail.explain(Button)
282
+ ref_3 = explanation_3.reference
283
+ if !ref_3.is_a?(Module)
284
+ assert_kind_of Mocktail::TypeReplacementData, ref_3
285
+ T.assert_type!(ref_3.calls, T::Array[Mocktail::Call])
286
+ T.assert_type!(ref.stubbings, T::Array[Mocktail::Stubbing[T.untyped]])
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,35 @@
1
+ # typed: true
2
+
3
+ require "mocktail/sorbet" # <-- the special sauce!
4
+ require "minitest/autorun"
5
+
6
+ require "tempfile"
7
+ require "open3"
8
+ module SorbetInsurance
9
+ include Kernel
10
+
11
+ def assert_type_failure(ruby_code, mode: "true")
12
+ Tempfile.create do |file|
13
+ file.write("# typed: strict\n\n" + ruby_code)
14
+ file.flush
15
+ stdout, stderr, status = Open3.capture3("bundle exec srb tc #{file.path}")
16
+ raise "Type passing succeeded but expected failure (stdout: #{stdout})" if status.success?
17
+ "#{stdout}\n#{stderr}"
18
+ end
19
+ end
20
+
21
+ def assert_strict_type_failure(ruby_code)
22
+ assert_type_failure(ruby_code, mode: "strict")
23
+ end
24
+ end
25
+
26
+ class Minitest::Test
27
+ include SorbetInsurance
28
+ include Mocktail::DSL
29
+
30
+ make_my_diffs_pretty!
31
+
32
+ def teardown
33
+ Mocktail.reset
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mocktail", path: "../.."
4
+ gem "minitest", "~> 5.26" # 6.0+ requires Ruby >= 3.2, dropping support for Ruby 3.1
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ mocktail (3.0.0)
5
+ sorbet-eraser (~> 0.3.1)
6
+ sorbet-runtime (~> 0.5.9204)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ minitest (5.27.0)
12
+ sorbet-eraser (0.3.1)
13
+ sorbet-runtime (0.5.11971)
14
+
15
+ PLATFORMS
16
+ arm64-darwin-22
17
+ arm64-darwin-24
18
+ arm64-darwin-25
19
+
20
+ DEPENDENCIES
21
+ minitest (~> 5.26)
22
+ mocktail!
23
+
24
+ BUNDLED WITH
25
+ 2.4.13
@@ -0,0 +1,54 @@
1
+ require "minitest/autorun"
2
+ require "mocktail"
3
+
4
+ class AntitypeTest < Minitest::Test
5
+ include Mocktail::DSL
6
+
7
+ module Stuff
8
+ def things(pants, shirts, sandals:)
9
+ end
10
+ end
11
+
12
+ def test_mocking_modules
13
+ stuff = Mocktail.of(Stuff)
14
+
15
+ stubs(ignore_arity: true, ignore_extra_args: true) { stuff.things }.with { "things" }
16
+
17
+ assert_equal "things", stuff.things(1, 2, sandals: 3)
18
+ end
19
+
20
+ class Stuffing
21
+ include Stuff
22
+ end
23
+
24
+ def test_matchers
25
+ stuff = Mocktail.of(Stuff)
26
+
27
+ stubs { stuff.things(:zubon, :shatsu, sandals: false) }.with { "things" }
28
+ stubs { |m| stuff.things(m.numeric, m.is_a(Stuff), sandals: m.includes(true)) }.with { "crap" }
29
+
30
+ assert_equal "things", stuff.things(:zubon, :shatsu, sandals: false)
31
+ assert_equal "crap", stuff.things(42, Stuffing.new, sandals: [true, false])
32
+ assert_nil stuff.things(42, Stuffing.new, sandals: ["true"])
33
+ assert_nil stuff.things("42", Stuffing.new, sandals: [true])
34
+ assert_nil stuff.things(42, Stuffing, sandals: [true])
35
+
36
+ stuff.things(:a, :b, sandals: :c)
37
+ stuff.things(:c, :d, sandals: :e)
38
+ stuff.things(:f, :g, sandals: :h)
39
+
40
+ captor = Mocktail.captor
41
+ verify { stuff.things(:c, captor.capture, sandals: :e) }
42
+ assert_equal captor.value, :d
43
+ end
44
+
45
+ def test_sorbet_runtime_is_not_loaded
46
+ refute $LOADED_FEATURES.any? { |feature|
47
+ feature.include?("sorbet-runtime")
48
+ }, "sorbet-runtime was required by something in mocktail. Features:\n\n#{$LOADED_FEATURES.join("\n")}"
49
+ end
50
+
51
+ def teardown
52
+ Mocktail.reset
53
+ end
54
+ end
@@ -0,0 +1,5 @@
1
+ #/usr/bin/env bash
2
+
3
+ set -xeuo pipefail
4
+
5
+ bundle exec ruby antitype_test.rb