mocktail 1.2.3 → 2.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 (183) 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 +14 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +98 -25
  8. data/README.md +18 -922
  9. data/Rakefile +0 -1
  10. data/bin/console +1 -2
  11. data/bin/tapioca +29 -0
  12. data/lib/mocktail/collects_calls.rb +2 -0
  13. data/lib/mocktail/debug.rb +13 -10
  14. data/lib/mocktail/dsl.rb +2 -0
  15. data/lib/mocktail/errors.rb +2 -0
  16. data/lib/mocktail/explains_nils.rb +2 -0
  17. data/lib/mocktail/explains_thing.rb +7 -4
  18. data/lib/mocktail/grabs_original_method_parameters.rb +30 -0
  19. data/lib/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +3 -1
  20. data/lib/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +5 -1
  21. data/lib/mocktail/handles_dry_call/fulfills_stubbing.rb +2 -0
  22. data/lib/mocktail/handles_dry_call/logs_call.rb +2 -0
  23. data/lib/mocktail/handles_dry_call/validates_arguments.rb +6 -4
  24. data/lib/mocktail/handles_dry_call.rb +2 -0
  25. data/lib/mocktail/handles_dry_new_call.rb +2 -0
  26. data/lib/mocktail/imitates_type/ensures_imitation_support.rb +2 -0
  27. data/lib/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +4 -1
  28. data/lib/mocktail/imitates_type/makes_double/declares_dry_class.rb +32 -20
  29. data/lib/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +2 -0
  30. data/lib/mocktail/imitates_type/makes_double.rb +3 -0
  31. data/lib/mocktail/imitates_type.rb +3 -1
  32. data/lib/mocktail/initialize_based_on_type_system_mode_switching.rb +9 -0
  33. data/lib/mocktail/initializes_mocktail.rb +5 -0
  34. data/lib/mocktail/matcher_presentation.rb +4 -2
  35. data/lib/mocktail/matchers/any.rb +4 -3
  36. data/lib/mocktail/matchers/base.rb +10 -2
  37. data/lib/mocktail/matchers/captor.rb +9 -0
  38. data/lib/mocktail/matchers/includes.rb +2 -0
  39. data/lib/mocktail/matchers/includes_hash.rb +9 -0
  40. data/lib/mocktail/matchers/includes_key.rb +9 -0
  41. data/lib/mocktail/matchers/includes_string.rb +9 -0
  42. data/lib/mocktail/matchers/is_a.rb +2 -0
  43. data/lib/mocktail/matchers/matches.rb +2 -0
  44. data/lib/mocktail/matchers/not.rb +2 -0
  45. data/lib/mocktail/matchers/numeric.rb +5 -4
  46. data/lib/mocktail/matchers/that.rb +2 -0
  47. data/lib/mocktail/matchers.rb +3 -0
  48. data/lib/mocktail/raises_neato_no_method_error.rb +2 -0
  49. data/lib/mocktail/records_demonstration.rb +2 -0
  50. data/lib/mocktail/registers_matcher.rb +8 -3
  51. data/lib/mocktail/registers_stubbing.rb +2 -0
  52. data/lib/mocktail/replaces_next.rb +7 -1
  53. data/lib/mocktail/replaces_type/redefines_new.rb +3 -1
  54. data/lib/mocktail/replaces_type/redefines_singleton_methods.rb +14 -2
  55. data/lib/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +37 -0
  56. data/lib/mocktail/replaces_type.rb +6 -0
  57. data/lib/mocktail/resets_state.rb +2 -0
  58. data/lib/mocktail/share/bind.rb +7 -5
  59. data/lib/mocktail/share/cleans_backtrace.rb +3 -5
  60. data/lib/mocktail/share/creates_identifier.rb +16 -9
  61. data/lib/mocktail/share/determines_matching_calls.rb +4 -2
  62. data/lib/mocktail/share/stringifies_call.rb +6 -2
  63. data/lib/mocktail/share/stringifies_method_name.rb +3 -1
  64. data/lib/mocktail/simulates_argument_error/reconciles_args_with_params.rb +2 -0
  65. data/lib/mocktail/simulates_argument_error/recreates_message.rb +2 -0
  66. data/lib/mocktail/simulates_argument_error/transforms_params.rb +15 -8
  67. data/lib/mocktail/simulates_argument_error.rb +2 -0
  68. data/lib/mocktail/sorbet/mocktail/collects_calls.rb +18 -0
  69. data/lib/mocktail/sorbet/mocktail/debug.rb +54 -0
  70. data/lib/mocktail/sorbet/mocktail/dsl.rb +46 -0
  71. data/lib/mocktail/sorbet/mocktail/errors.rb +19 -0
  72. data/lib/mocktail/sorbet/mocktail/explains_nils.rb +41 -0
  73. data/lib/mocktail/sorbet/mocktail/explains_thing.rb +137 -0
  74. data/lib/mocktail/sorbet/mocktail/grabs_original_method_parameters.rb +33 -0
  75. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/describes_unsatisfied_stubbing.rb +27 -0
  76. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing/finds_satisfaction.rb +24 -0
  77. data/lib/mocktail/sorbet/mocktail/handles_dry_call/fulfills_stubbing.rb +45 -0
  78. data/lib/mocktail/sorbet/mocktail/handles_dry_call/logs_call.rb +12 -0
  79. data/lib/mocktail/sorbet/mocktail/handles_dry_call/validates_arguments.rb +45 -0
  80. data/lib/mocktail/sorbet/mocktail/handles_dry_call.rb +25 -0
  81. data/lib/mocktail/sorbet/mocktail/handles_dry_new_call.rb +42 -0
  82. data/lib/mocktail/sorbet/mocktail/imitates_type/ensures_imitation_support.rb +16 -0
  83. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class/reconstructs_call.rb +73 -0
  84. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/declares_dry_class.rb +136 -0
  85. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double/gathers_fakeable_instance_methods.rb +28 -0
  86. data/lib/mocktail/sorbet/mocktail/imitates_type/makes_double.rb +29 -0
  87. data/lib/mocktail/sorbet/mocktail/imitates_type.rb +29 -0
  88. data/lib/mocktail/sorbet/mocktail/initialize_based_on_type_system_mode_switching.rb +11 -0
  89. data/lib/mocktail/sorbet/mocktail/initializes_mocktail.rb +25 -0
  90. data/lib/mocktail/sorbet/mocktail/matcher_presentation.rb +21 -0
  91. data/lib/mocktail/sorbet/mocktail/matchers/any.rb +27 -0
  92. data/lib/mocktail/sorbet/mocktail/matchers/base.rb +39 -0
  93. data/lib/mocktail/sorbet/mocktail/matchers/captor.rb +76 -0
  94. data/lib/mocktail/sorbet/mocktail/matchers/includes.rb +32 -0
  95. data/lib/mocktail/sorbet/mocktail/matchers/includes_hash.rb +12 -0
  96. data/lib/mocktail/sorbet/mocktail/matchers/includes_key.rb +12 -0
  97. data/lib/mocktail/sorbet/mocktail/matchers/includes_string.rb +12 -0
  98. data/lib/mocktail/sorbet/mocktail/matchers/is_a.rb +17 -0
  99. data/lib/mocktail/sorbet/mocktail/matchers/matches.rb +19 -0
  100. data/lib/mocktail/sorbet/mocktail/matchers/not.rb +17 -0
  101. data/lib/mocktail/sorbet/mocktail/matchers/numeric.rb +27 -0
  102. data/lib/mocktail/sorbet/mocktail/matchers/that.rb +32 -0
  103. data/lib/mocktail/sorbet/mocktail/matchers.rb +19 -0
  104. data/lib/mocktail/sorbet/mocktail/raises_neato_no_method_error.rb +93 -0
  105. data/lib/mocktail/sorbet/mocktail/records_demonstration.rb +43 -0
  106. data/lib/mocktail/sorbet/mocktail/registers_matcher.rb +65 -0
  107. data/lib/mocktail/sorbet/mocktail/registers_stubbing.rb +31 -0
  108. data/lib/mocktail/sorbet/mocktail/replaces_next.rb +55 -0
  109. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_new.rb +32 -0
  110. data/lib/mocktail/sorbet/mocktail/replaces_type/redefines_singleton_methods.rb +80 -0
  111. data/lib/mocktail/sorbet/mocktail/replaces_type/runs_sorbet_sig_blocks_before_replacement.rb +39 -0
  112. data/lib/mocktail/sorbet/mocktail/replaces_type.rb +36 -0
  113. data/lib/mocktail/sorbet/mocktail/resets_state.rb +14 -0
  114. data/lib/mocktail/sorbet/mocktail/share/bind.rb +18 -0
  115. data/lib/mocktail/sorbet/mocktail/share/cleans_backtrace.rb +22 -0
  116. data/lib/mocktail/sorbet/mocktail/share/creates_identifier.rb +39 -0
  117. data/lib/mocktail/sorbet/mocktail/share/determines_matching_calls.rb +72 -0
  118. data/lib/mocktail/sorbet/mocktail/share/stringifies_call.rb +85 -0
  119. data/lib/mocktail/sorbet/mocktail/share/stringifies_method_name.rb +16 -0
  120. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/reconciles_args_with_params.rb +27 -0
  121. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/recreates_message.rb +34 -0
  122. data/lib/mocktail/sorbet/mocktail/simulates_argument_error/transforms_params.rb +58 -0
  123. data/lib/mocktail/sorbet/mocktail/simulates_argument_error.rb +36 -0
  124. data/lib/mocktail/sorbet/mocktail/sorbet.rb +3 -0
  125. data/lib/mocktail/sorbet/mocktail/stringifies_method_signature.rb +53 -0
  126. data/lib/mocktail/sorbet/mocktail/typed.rb +5 -0
  127. data/lib/mocktail/sorbet/mocktail/value/cabinet.rb +91 -0
  128. data/lib/mocktail/sorbet/mocktail/value/call.rb +51 -0
  129. data/lib/mocktail/sorbet/mocktail/value/demo_config.rb +10 -0
  130. data/lib/mocktail/sorbet/mocktail/value/double.rb +10 -0
  131. data/lib/mocktail/sorbet/mocktail/value/double_data.rb +15 -0
  132. data/lib/mocktail/sorbet/mocktail/value/explanation.rb +68 -0
  133. data/lib/mocktail/sorbet/mocktail/value/explanation_data.rb +19 -0
  134. data/lib/mocktail/sorbet/mocktail/value/fake_method_data.rb +11 -0
  135. data/lib/mocktail/sorbet/mocktail/value/matcher_registry.rb +27 -0
  136. data/lib/mocktail/sorbet/mocktail/value/no_explanation_data.rb +20 -0
  137. data/lib/mocktail/sorbet/mocktail/value/signature.rb +35 -0
  138. data/lib/mocktail/sorbet/mocktail/value/stubbing.rb +26 -0
  139. data/lib/mocktail/sorbet/mocktail/value/top_shelf.rb +79 -0
  140. data/lib/mocktail/sorbet/mocktail/value/type_replacement.rb +11 -0
  141. data/lib/mocktail/sorbet/mocktail/value/type_replacement_data.rb +19 -0
  142. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call.rb +9 -0
  143. data/lib/mocktail/sorbet/mocktail/value/unsatisfying_call_explanation.rb +24 -0
  144. data/lib/mocktail/sorbet/mocktail/value.rb +19 -0
  145. data/lib/mocktail/sorbet/mocktail/verifies_call/finds_verifiable_calls.rb +21 -0
  146. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +15 -0
  147. data/lib/mocktail/sorbet/mocktail/verifies_call/raises_verification_error.rb +74 -0
  148. data/lib/mocktail/sorbet/mocktail/verifies_call.rb +37 -0
  149. data/lib/mocktail/sorbet/mocktail/version.rb +12 -0
  150. data/lib/mocktail/sorbet/mocktail.rb +154 -0
  151. data/lib/mocktail/sorbet.rb +1 -0
  152. data/lib/mocktail/stringifies_method_signature.rb +2 -0
  153. data/lib/mocktail/typed.rb +3 -0
  154. data/lib/mocktail/value/cabinet.rb +8 -1
  155. data/lib/mocktail/value/call.rb +44 -12
  156. data/lib/mocktail/value/demo_config.rb +6 -7
  157. data/lib/mocktail/value/double.rb +6 -7
  158. data/lib/mocktail/value/double_data.rb +11 -7
  159. data/lib/mocktail/value/explanation.rb +28 -3
  160. data/lib/mocktail/value/explanation_data.rb +14 -0
  161. data/lib/mocktail/value/fake_method_data.rb +7 -6
  162. data/lib/mocktail/value/matcher_registry.rb +2 -0
  163. data/lib/mocktail/value/no_explanation_data.rb +16 -0
  164. data/lib/mocktail/value/signature.rb +19 -27
  165. data/lib/mocktail/value/stubbing.rb +11 -12
  166. data/lib/mocktail/value/top_shelf.rb +5 -0
  167. data/lib/mocktail/value/type_replacement.rb +7 -8
  168. data/lib/mocktail/value/type_replacement_data.rb +10 -7
  169. data/lib/mocktail/value/unsatisfying_call.rb +5 -6
  170. data/lib/mocktail/value/unsatisfying_call_explanation.rb +18 -0
  171. data/lib/mocktail/value.rb +5 -2
  172. data/lib/mocktail/verifies_call/finds_verifiable_calls.rb +2 -0
  173. data/lib/mocktail/verifies_call/raises_verification_error/gathers_calls_of_method.rb +2 -0
  174. data/lib/mocktail/verifies_call/raises_verification_error.rb +2 -0
  175. data/lib/mocktail/verifies_call.rb +3 -0
  176. data/lib/mocktail/version.rb +8 -1
  177. data/lib/mocktail.rb +46 -5
  178. data/mocktail.gemspec +8 -4
  179. data/rbi/mocktail-pregenerated.rbi +1865 -0
  180. data/rbi/mocktail.rbi +77 -0
  181. data/rbi/sorbet-runtime.rbi +29 -0
  182. data/spoom_report.html +1248 -0
  183. metadata +130 -3
data/README.md CHANGED
@@ -1,922 +1,18 @@
1
- <img
2
- src="https://user-images.githubusercontent.com/79303/134366631-9c6cfe67-a9c0-4096-bbea-ba1698a85b0b.png"
3
- width="90%"/>
4
-
5
- # Mocktail 🍸
6
-
7
- Mocktail is a [test
8
- double](https://github.com/testdouble/contributing-tests/wiki/Test-Double)
9
- library for Ruby that provides a terse and robust API for creating mocks,
10
- getting them in the hands of the code you're testing, stub & verify behavior,
11
- and even safely override class methods.
12
-
13
- If you'd prefer a voice & video introduction to Mocktail aside from this README,
14
- you might enjoy this ⚡️[Lightning
15
- Talk](https://blog.testdouble.com/talks/2022-05-18-please-mock-me?utm_source=twitter&utm_medium=organic-social&utm_campaign=conf-talk)⚡️
16
- from RailsConf 2022.
17
-
18
- ## An aperitif
19
-
20
- Before getting into the details, let's demonstrate what Mocktail's API looks
21
- like. Suppose you want to test a `Bartender` class:
22
-
23
- ```ruby
24
- class Bartender
25
- def initialize
26
- @shaker = Shaker.new
27
- @glass = Glass.new
28
- @bar = Bar.new
29
- end
30
-
31
- def make_drink(name, customer:)
32
- if name == :negroni
33
- drink = @shaker.combine(:gin, :campari, :sweet_vermouth)
34
- @glass.pour!(drink)
35
- @bar.pass(@glass, to: customer)
36
- end
37
- end
38
- end
39
- ```
40
-
41
- You could write an isolated unit test with Mocktail like this:
42
-
43
- ```ruby
44
- shaker = Mocktail.of_next(Shaker)
45
- glass = Mocktail.of_next(Glass)
46
- bar = Mocktail.of_next(Bar)
47
- subject = Bartender.new
48
- stubs { shaker.combine(:gin, :campari, :sweet_vermouth) }.with { :a_drink }
49
- stubs { bar.pass(glass, to: "Eileen") }.with { "🎉" }
50
-
51
- result = subject.make_drink(:negroni, customer: "Eileen")
52
-
53
- assert_equal "🎉", result
54
- # Oh yeah, and make sure the drink got poured! Silly side effects!
55
- verify { glass.pour!(:a_drink) }
56
- ```
57
-
58
- ## Why Mocktail?
59
-
60
- Besides helping you avoid a hangover, Mocktail offers several advantages over
61
- Ruby's other mocking libraries:
62
-
63
- * **Simpler test recipes**: [Mocktail.of_next(type)](#mocktailof_next) both
64
- creates your mock and supplies to your subject under test in a single
65
- one-liner. No more forcing dependency injection for the sake of your tests
66
- * **WYSIWYG API**: Want to know how to stub a call to `phone.dial(911)`? You
67
- just demonstrate the call with `stubs { phone.dial(911) }.with { :operator }`.
68
- Because stubbing & verifying looks just like the actual call, your tests
69
- becomes a sounding board for your APIs as you invent them
70
- * **Argument validation**: Ever see a test pass after a change to a mocked
71
- method should have broken it? Not with Mocktail, you haven't
72
- * **Mocked class methods**: Singleton methods on modules and classes can be
73
- faked out using [`Mocktail.replace(type)`](#mocktailreplace) without
74
- sacrificing thread safety
75
- * **Super-duper detailed error messages** A good mocking library should make
76
- coding feel like
77
- [paint-by-number](https://en.wikipedia.org/wiki/Paint_by_number), thoughtfully
78
- guiding you from one step to the next. Calling a method that doesn't exist
79
- will print a sample definition you can copy-paste. Verification failures will
80
- print every call that _did_ occur. And [Mocktail.explain()](#mocktailexplain)
81
- provides even more introspection
82
- * **Expressive**: Built-in [argument matchers](#mocktailmatchers) and a simple
83
- API for adding [custom matchers](#custom-matchers) allow you to tune your
84
- stubbing configuration and call verification to match _exactly_ what your test
85
- intends
86
- * **Powerful**: [Argument captors](#mocktailcaptor) for assertions of very
87
- complex arguments, as well as advanced configuration options for stubbing &
88
- verification
89
-
90
- ## Ready to order?
91
-
92
- ### Install the gem
93
-
94
- The main ingredient to add to your Gemfile:
95
-
96
- ```ruby
97
- gem "mocktail", group: :test
98
- ```
99
-
100
- ### Sprinkle in the DSL
101
-
102
- Then, in each of your tests or in a test helper, you'll probably want to include
103
- Mocktail's DSL. (This is optional, however, as every method in the DSL is also
104
- available as a singleton method on `Mocktail`.)
105
-
106
- In Minitest, you might add the DSL with:
107
-
108
- ```ruby
109
- class Minitest::Test
110
- include Mocktail::DSL
111
- end
112
- ```
113
-
114
- Or, in RSpec:
115
-
116
- ```ruby
117
- RSpec.configure do |config|
118
- config.include Mocktail::DSL
119
- end
120
- ```
121
-
122
- ### Clean up when you're done
123
-
124
- To reset Mocktail's internal state between tests and avoid test pollution, you
125
- should also call `Mocktail.reset` after each test:
126
-
127
- In Minitest:
128
-
129
- ```ruby
130
- class Minitest::Test
131
- # Or, if in a Rails test, in a `teardown do…end` block
132
- def teardown
133
- Mocktail.reset
134
- end
135
- end
136
- ```
137
-
138
- And RSpec:
139
-
140
- ```ruby
141
- RSpec.configure do |config|
142
- config.after(:each) do
143
- Mocktail.reset
144
- end
145
- end
146
- ```
147
-
148
- ## API
149
-
150
- The entire public API is listed in the [top-level module's
151
- source](lib/mocktail.rb). Below is a longer menu to explain what goes into each
152
- feature.
153
-
154
- ### Mocktail.of
155
-
156
- `Mocktail.of(module_or_class)` takes a module or class and returns an instance
157
- of an object with fake methods in place of all its instance methods which can
158
- then be stubbed or verified.
159
-
160
- ```ruby
161
- class Clothes; end;
162
- class Shoe < Clothes
163
- def tie(laces)
164
- end
165
- end
166
-
167
- shoe = Mocktail.of(Shoe)
168
- shoe.instance_of?(Shoe) # => true
169
- shoe.is_a?(Clothes) # => true
170
- shoe.class == Shoe # => false!
171
- shoe.to_s # => #<Mocktail of Shoe:0x00000001343b57b0>"
172
- ```
173
-
174
- ### Mocktail.of_next
175
-
176
- `Mocktail.of_next(klass, [count: 1])` takes a class and returns one mock (the
177
- default) or an array of multiple mocks. It also effectively overrides the
178
- behavior of that class's constructor to return those mock(s) in order and
179
- finally restoring its previous behavior.
180
-
181
- For example, if you wanted to test the `Notifier` class below:
182
-
183
- ```ruby
184
- class Notifier
185
- def initialize
186
- @mailer = Mailer.new
187
- end
188
-
189
- def notify(name)
190
- @mailer.deliver!("Hello, #{name}")
191
- end
192
- end
193
- ```
194
-
195
- You could write a test like this:
196
-
197
- ```ruby
198
- def test_notifier
199
- mailer = Mocktail.of_next(Mailer)
200
- subject = Notifier.new
201
-
202
- subject.notify("Pants")
203
-
204
- verify { mailer.deliver!("Hello, Pants") }
205
- end
206
- ```
207
-
208
- There's nothing wrong with creating mocks using `Mocktail.of` and passing them
209
- to your subject some other way, but this approach allows you to write very terse
210
- isolation tests without foisting additional indirection or dependency injection
211
- in for your tests' sake.
212
-
213
- ### Mocktail.stubs
214
-
215
- Configuring a fake method to take a certain action or return a particular value
216
- is called "stubbing". To stub a call with a value, you can call `Mocktail.stubs`
217
- (or just `stubs` if you've included `Mocktail::DSL`) and then specify an effect
218
- that will be invoked whenever that call configuration is satisfied using `with`.
219
-
220
- The API is very simple in the simple case:
221
-
222
- ```ruby
223
- class UserRepository
224
- def find(id, debug: false); end
225
-
226
- def transaction(&blk); end
227
- end
228
- ```
229
-
230
- You could stub responses to a mock of the `UserRepository` like this:
231
-
232
- ```ruby
233
- user_repository = Mocktail.of(UserRepository)
234
-
235
- stubs { user_repository.find(42) }.with { :a_user }
236
- user_repository.find(42) # => :a_user
237
- user_repository.find(43) # => nil
238
- user_repository.find # => ArgumentError: wrong number of arguments (given 0, expected 1)
239
- ```
240
-
241
- The block passed to `stubs` is called the "demonstration", because it represents
242
- an example of the kind of calls that Mocktail should match.
243
-
244
- If you want to get fancy, you can use matchers to make your demonstration more
245
- dynamic. For example, you could match any number with:
246
-
247
- ```ruby
248
- stubs { |m| user_repository.find(m.numeric) }.with { :another_user }
249
- user_repository.find(41) # => :another_user
250
- user_repository.find(42) # => :another_user
251
- user_repository.find(43) # => :another_user
252
- ```
253
-
254
- Stubbings are last-in-wins, which is why the stubbing above would have
255
- overridden the earlier-but-more-specific stubbing of `find(42)`.
256
-
257
- A stubbing's effect can also be changed dynamically based on the actual call
258
- that satisfied the demonstration by looking at the `call` block argument:
259
-
260
- ```ruby
261
- stubs { |m| user_repository.find(m.is_a(Integer)) }.with { |call|
262
- {id: call.args.first}
263
- }
264
- user_repository.find(41) # => {id: 41}
265
- # Since 42.5 is a Float, the earlier stubbing will win here:
266
- user_repository.find(42.5) # => :another_user
267
- user_repository.find(43) # => {id: 43}
268
- ```
269
-
270
- It's certainly more complex to think through, but if your stubbed method takes a
271
- block, your demonstration can pass a block of its own and inspect or invoke it:
272
-
273
- ```ruby
274
- stubs {
275
- user_repository.transaction { |block| block.call == {id: 41} }
276
- }.with { :successful_transaction }
277
-
278
- user_repository.transaction {
279
- user_repository.find(41)
280
- } # => :successful_transaction
281
- user_repository.transaction {
282
- user_repository.find(40)
283
- } # => nil
284
- ```
285
-
286
- There are also several advanced options you can pass to `stubs` to control its
287
- behavior.
288
-
289
- `times` will limit the number of times a satisfied stubbing can have its effect:
290
-
291
- ```ruby
292
- stubs { |m| user_repository.find(m.any) }.with { :not_found }
293
- stubs(times: 2) { |m| user_repository.find(1) }.with { :someone }
294
-
295
- user_repository.find(1) # => :someone
296
- user_repository.find(1) # => :someone
297
- user_repository.find(1) # => :not_found
298
- ```
299
-
300
- `ignore_extra_args` will allow a demonstration to be considered satisfied even
301
- if it fails to specify arguments and keyword arguments made by the actual call:
302
-
303
- ```ruby
304
- stubs { user_repository.find(4) }.with { :a_person }
305
- user_repository.find(4, debug: true) # => nil
306
-
307
- stubs(ignore_extra_args: true) { user_repository.find(4) }.with { :b_person }
308
- user_repository.find(4, debug: true) # => :b_person
309
- ```
310
-
311
- And `ignore_block` will similarly allow a demonstration to not concern itself
312
- with whether an actual call passed the method a block—it's satisfied either way:
313
-
314
- ```ruby
315
- stubs { user_repository.transaction }.with { :transaction }
316
- user_repository.transaction {} # => nil
317
-
318
- stubs(ignore_block: true) { user_repository.transaction }.with { :transaction }
319
- user_repository.transaction {} # => :transaction
320
- ```
321
-
322
- ### Mocktail.verify
323
-
324
- In practice, we've found that we stub far more responses than we explicitly
325
- verify a particular call took place. That's because our code normally returns
326
- some observable value that is _influenced_ by our dependencies' behavior, so
327
- adding additional assertions that they be called would be redundant. That
328
- said, for cases where a dependency doesn't return a value but just has a
329
- necessary side effect, the `verify` method exists (and like `stubs` is included
330
- in `Mocktail::DSL`).
331
-
332
- Once you've gotten the hang of stubbing, you'll find that the `verify` method is
333
- intentionally very similar. They almost rhyme.
334
-
335
- For this example, consider an `Auditor` class that our code might need to call
336
- to record that certain actions took place.
337
-
338
- ```ruby
339
- class Auditor
340
- def record!(message, user_id:, action: nil); end
341
- end
342
- ```
343
-
344
- Once you've created a mock of the `Auditor`, you can start verifying basic
345
- calls:
346
-
347
- ```ruby
348
- auditor = Mocktail.of(Auditor)
349
-
350
- verify { auditor.record!("hello", user_id: 42) }
351
- # => raised Mocktail::VerificationError
352
- # Expected mocktail of Auditor#record! to be called like:
353
- #
354
- # record!("hello", user_id: 42)
355
- #
356
- # But it was never called.
357
- ```
358
-
359
- Wups! Verify will blow up whenever a matching call hasn't occurred, so it
360
- should be called after you've invoked your subject under test along with any
361
- other assertions you have.
362
-
363
- If we make a call that satisfies the `verify` call's demonstration, however, you
364
- won't see that error:
365
-
366
- ```ruby
367
- auditor.record!("hello", user_id: 42)
368
-
369
- verify { auditor.record!("hello", user_id: 42) } # => nil
370
- ```
371
-
372
- There, nothing happened! Just like any other assertion library, you only hear
373
- from `verify` when verification fails.
374
-
375
- Just like with `stubs`, you can any built-in or custom matchers can serve as
376
- garnishes for your demonstration:
377
-
378
- ```ruby
379
- auditor.record!("hello", user_id: 42)
380
-
381
- verify { |m| auditor.record!(m.is_a(String), user_id: m.numeric) } # => nil
382
- # But this will raise a VerificationError:
383
- verify { |m| auditor.record!(m.is_a(String), user_id: m.that { |arg| arg > 50}) }
384
- ```
385
-
386
- When you pass a block to your demonstration, it will be invoked with any block
387
- that was passed to the actual call to the mock. Truthy responses will satisfy
388
- the verification and falsey ones will fail:
389
-
390
- ```ruby
391
- auditor.record!("ok", user_id: 1) { Time.new }
392
-
393
- verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Time) } } # => nil
394
- # But this will raise a VerificationError:
395
- verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Date) } }
396
- ```
397
-
398
- `verify` supports the same options as `stubs`:
399
-
400
- * `times` will require the demonstrated call happened exactly `times` times (by
401
- default, the call has to happen 1 or more times)
402
- * `ignore_extra_args` will allow the demonstration to forego specifying optional
403
- arguments while still being considered satisfied
404
- * `ignore_block` will similarly allow the demonstration to forego specifying a
405
- block, even if the actual call receives one
406
-
407
- Note that if you want to verify a method _wasn't_ called at all or called a
408
- specific number of times—especially if you don't care about the parameters, you
409
- may want to look at the [Mocktail.calls()](#mocktailcalls) API.
410
-
411
- ### Mocktail.matchers
412
-
413
- You'll probably never need to call `Mocktail.matchers` directly, because it's
414
- the object that is passed to every demonstration block passed to `stubs` and
415
- `verify`. By default, a stubbing (e.g. `stubs { email.send("text") }`) is only
416
- considered satisfied if every argument passed to an actual call was passed an
417
- `==` check. Matchers allow us to relax or change that constraint for both
418
- regular arguments and keyword arguments so that our demonstrations can match
419
- more kinds of method invocations.
420
-
421
- Matchers allow you to specify stubbings and verifications that look like this:
422
-
423
- ```ruby
424
- stubs { |m| email.send(m.is_a(String)) }.with { "I'm an email" }
425
- ```
426
-
427
- #### Built-in matchers
428
-
429
- These matchers come out of the box:
430
-
431
- * `any` - Will match any value (even nil) in the given argument position or
432
- keyword
433
- * `is_a(type)` - Will match when its `type` passes an `is_a?` check against the
434
- actual argument
435
- * `includes(thing, [**more_things])` - Will match when all of its arguments are
436
- contained by the corresponding argument—be it a string, array, hash, or
437
- anything that responds to `includes?`
438
- * `matches(pattern)` - Will match when the provided string or pattern passes
439
- a `match?` test on the corresponding argument; usually used to match strings
440
- that contain a particular substring or pattern, but will work with any
441
- argument that responds to `match?`
442
- * `not(thing)` - Will only match when its argument _does not_ equal (via `!=`)
443
- the actual argument
444
- * `numeric` - Will match when the actual argument is an instance of `Integer`,
445
- `Float`, or (if loaded) `BigDecimal`
446
- * `that { |arg| … }` - Takes a block that will receive the actual argument. If
447
- the block returns truthy, it's considered a match; otherwise, it's not a
448
- match.
449
-
450
- #### Custom matchers
451
-
452
- If you want to write your own matchers, check out [the source for
453
- examples](lib/mocktail/matchers/includes.rb). Once you've implemented a class,
454
- just pass it to `Mocktail.register_matcher` in your test helper.
455
-
456
- ```ruby
457
- class MyAwesomeMatcher < Mocktail::Matchers::Base
458
- def self.matcher_name
459
- :awesome
460
- end
461
-
462
- def match?(actual)
463
- "#{@expected}✨" == actual
464
- end
465
- end
466
-
467
- Mocktail.register_matcher(MyAwesomeMatcher)
468
- ```
469
-
470
- Then, a stubbing like this:
471
-
472
- ```ruby
473
- stubs { |m| user_repository.find(m.awesome(11)) }.with { :awesome_user }
474
-
475
- user_repository.find("11")) # => nil
476
- user_repository.find("11✨")) # => :awesome_user
477
- ```
478
-
479
- ### Mocktail.captor
480
-
481
- An argument captor is a special kind of matcher… really, it's a matcher factory.
482
- Suppose you have a `verify` call for which one of the expected arguments is
483
- _really_ complicated. Since `verify` tends to be paired with fire-and-forget
484
- APIs that are being invoked for the side effect, this is a pretty common case.
485
- You want to be able to effectively snag that value and then run any number of
486
- specific assertions against it.
487
-
488
- That's what `Mocktail.captor` is for. It's easiest to make sense of this by
489
- example. Given this `BigApi` class that's presumably being called by your
490
- subject at the end of a lot of other work building up a payload:
491
-
492
- ```ruby
493
- class BigApi
494
- def send(payload); end
495
- end
496
- ```
497
-
498
- You could capture the value of that payload as part of the verification of the
499
- call:
500
-
501
- ```ruby
502
- big_api = Mocktail.of(BigApi)
503
-
504
- big_api.send({imagine: "that", this: "is", a: "huge", object: "!"})
505
-
506
- payload_captor = Mocktail.captor
507
- verify { big_api.send(payload_captor.capture) } # => nil!
508
- ```
509
-
510
- The `verify` above will pass because _a_ call did happen, but we haven't
511
- asserted anything beyond that yet. What really happened is that
512
- `payload_captor.capture` actually returned a matcher that will return true for
513
- any argument _while also sneakily storing a copy of the argument value_.
514
-
515
- That's why we instantiated `payload_captor` with `Mocktail.captor` outside the
516
- demonstration block, so we can inspect its `value` after the `verify` call:
517
-
518
- ```ruby
519
- payload_captor = Mocktail.captor
520
- verify { big_api.send(payload_captor.capture) } # => nil!
521
-
522
- payload = payload_captor.value # {:imagine=>"that", :this=>"is", :a=>"huge", :object=>"!"}
523
- assert_equal "huge", payload[:a]
524
- ```
525
-
526
- ### Mocktail.replace
527
-
528
- Mocktail was written to support isolated test-driven development, which usually
529
- results in a lot of boring classes and instance methods. But sometimes you need
530
- to mock singleton methods on classes or modules, and we support that too.
531
-
532
- When you call `Mocktail.replace(type)`, all of the singleton methods on the
533
- provided type are replaced with fake methods available for stubbing and
534
- verification. It's really that simple.
535
-
536
- For example, if our `Bartender` class has a class method:
537
-
538
- ```ruby
539
- class Bartender
540
- def self.cliche_greeting
541
- ["It's 5 o'clock somewhere!", "Norm!"].sample
542
- end
543
- end
544
- ```
545
-
546
- We can replace the behavior of the overall class, and then stub how we'd like it
547
- to respond, in our test:
548
-
549
- ```ruby
550
- Mocktail.replace(Bartender)
551
- stubs { Bartender.cliche_greeting }.with { "Norm!" }
552
- ```
553
-
554
- [**Obligatory warning:** Mocktail does its best to ensure that other threads
555
- won't be affected when you replace the singleton methods on a type, but your
556
- mileage may very! Singleton methods are global and code that introspects or
557
- invokes a replaced method in a peculiar-enough way could lead to hard-to-track
558
- down bugs. (If this concerns you, then the fact that class methods are
559
- effectively global state may be a great reason not to rely too heavily on
560
- them!)]
561
-
562
- ### Mocktail.explain
563
-
564
- Test debugging is hard enough when there _aren't_ fake objects flying every
565
- which way, so Mocktail tries to make it a little easier on you. In addition to
566
- returning useful messages throughout the API, the gem also includes an
567
- introspection method `Mocktail.explain(thing)`, which returns a human-readable
568
- `message` and a `reference` object with useful attributes (that vary depending
569
- on the type of fake `thing` you pass in. Below are some things `explain()` can
570
- do.
571
-
572
- #### Fake instances created by Mocktail
573
-
574
- Any instances created by `Mocktail.of` or `Mocktail.of_next` can be passed to
575
- `Mocktail.explain`, and they will list out all the calls and stubbings made for
576
- each of their fake methods.
577
-
578
- Suppose these interactions have occurred:
579
-
580
- ```ruby
581
- ice_tray = Mocktail.of(IceTray)
582
-
583
- Mocktail.stubs { ice_tray.fill(:tap_water, 30) }.with { :some_ice }
584
-
585
- ice_tray.fill(:tap_water, 50)
586
- ```
587
-
588
- You can interrogate what's going on with the fake instance by passing it to
589
- `explain`:
590
-
591
- ```ruby
592
- explanation = Mocktail.explain(ice_tray)
593
-
594
- explanation.reference.type #=> IceTray
595
- explanation.reference.double #=> The ice_tray instance
596
- explanation.reference.calls #=> details on each invocation of each method
597
- explanation.reference.stubbings #=> all stubbings configured for each method
598
- ```
599
-
600
- Calling `explanation.message` will return:
601
-
602
- ```
603
- This is a fake `IceTray' instance.
604
-
605
- It has these mocked methods:
606
- - fill
607
-
608
- `IceTray#fill' stubbings:
609
-
610
- fill(:tap_water, 30)
611
-
612
- `IceTray#fill' calls:
613
-
614
- fill(:tap_water, 50)
615
-
616
- ```
617
-
618
- #### Modules and classes with singleton methods replaced
619
-
620
- If you've called `Mocktail.replace()` on a class or module, it can also be
621
- passed to `Mocktail.explain()` for a summary of all the stubbing configurations
622
- and calls made against its faked singleton methods for the currently running
623
- thread.
624
-
625
- Imagine a `Shop` class with `self.open!` and `self.close!` singleton methods:
626
-
627
- ```ruby
628
- Mocktail.replace(Shop)
629
-
630
- stubs { |m| Shop.open!(m.numeric) }.with { :a_bar }
631
-
632
- Shop.open!(42)
633
-
634
- Shop.close!(42)
635
-
636
- explanation = Mocktail.explain(Shop)
637
-
638
- explanation.reference.type #=> Shop
639
- explanation.reference.replaced_method_names #=> [:close!, :open!]
640
- explanation.reference.calls #=> details on each invocation of each method
641
- explanation.reference.stubbings #=> all stubbings configured for each method
642
- ```
643
-
644
- And `explanation.message` will return:
645
-
646
- ```ruby
647
- `Shop' is a class that has had its singleton methods faked.
648
-
649
- It has these mocked methods:
650
- - close!
651
- - open!
652
-
653
- `Shop.close!' has no stubbings.
654
-
655
- `Shop.close!' calls:
656
-
657
- close!(42)
658
-
659
- close!(42)
660
-
661
- `Shop.open!' stubbings:
662
-
663
- open!(numeric)
664
-
665
- open!(numeric)
666
-
667
- `Shop.open!' calls:
668
-
669
- open!(42)
670
-
671
- open!(42)
672
- ```
673
-
674
- #### Methods on faked instances and replaced types
675
-
676
- In addition to passing the test double, you can also pass a reference to any
677
- fake method created by Mocktail to `Mocktail.explain`:
678
-
679
- ```ruby
680
- ice_tray = Mocktail.of(IceTray)
681
-
682
- ice_tray.fill(:chilled, 50)
683
-
684
- explanation = Mocktail.explain(ice_tray.method(:fill))
685
-
686
- explanation.reference.receiver #=> a reference to the `ice_tray` instance
687
- explanation.reference.calls #=> details on each invocation of the method
688
- explanation.reference.stubbings #=> all stubbings configured for the method
689
- ```
690
-
691
- The above may be handy in cases where you want to assert the number of calls of
692
- a method outside the `Mocktail.verify` API:
693
-
694
- ```ruby
695
- assert_equal 1, explanation.reference.calls.size
696
- ```
697
-
698
- The explanation will also contain a `message` like this:
699
-
700
- ```
701
- `IceTray#fill' has no stubbings.
702
-
703
- `IceTray#fill' calls:
704
-
705
- fill(:chilled, 50)
706
- ```
707
-
708
- Replaced singleton methods can also be passed to `explain()`, so something like
709
- `Mocktail.explain(Shop.method(:open!))` from the earlier example would also work
710
- (with `Shop` being the `receiver` on the explanation's `reference`).
711
-
712
- #### Undefined methods
713
-
714
- There's no API for this one, but Mocktail also offers explanations for methods
715
- that don't exist yet. You'll see this error message whenever you try to call a
716
- method that doesn't exist on a test double. The message is designed to
717
- facilitate "paint-by-numbers" TDD, by including a sample definition of the
718
- method you had attempted to call that can be copy-pasted into a source listing:
719
-
720
- ```ruby
721
- class IceTray
722
- end
723
-
724
- ice_tray = Mocktail.of(IceTray)
725
-
726
- ice_tray.fill(:water_type, 30)
727
- # => No method `IceTray#fill' exists for call: (NoMethodError)
728
- #
729
- # fill(:water_type, 30)
730
- #
731
- # Need to define the method? Here's a sample definition:
732
- #
733
- # def fill(water_type, arg)
734
- # end
735
- ```
736
-
737
- From there, you can just copy-paste the provided method stub as a starting point
738
- for your new method:
739
-
740
- ```ruby
741
- class IceTray
742
- def fill(water_type, amount)
743
- end
744
- end
745
- ```
746
-
747
- ### Mocktail.explain_nils
748
-
749
- Is a faked method returning `nil` and you don't understand why?
750
-
751
- By default, methods faked by Mocktail will return `nil` when no stubbing is
752
- satisfied. A frequent frustration, therefore, is when the way `stubs {}.with {}`
753
- is configured does not satisfy a call the way you expected. To try to make
754
- debugging this a little bit easier, the gem provides a top-level
755
- `Mocktail.explain_nils` method that will return an array of summaries of every
756
- call to a faked method that failed to satisfy any stubbings.
757
-
758
- For example, suppose you stub this `fill` method like so:
759
-
760
- ```ruby
761
- ice_tray = Mocktail.of(IceTray)
762
-
763
- stubs { ice_tray.fill(:tap_water, 30) }.with { :normal_ice }
764
- ```
765
-
766
- But then you find that your subject under test is just getting `nil` back and
767
- you don't understand why:
768
-
769
- ```ruby
770
- def prep
771
- ice = ice_tray.fill(:tap_water, 50)
772
- glass.add(ice) # => why is `ice` nil?!
773
- end
774
- ```
775
-
776
- Whenever you're confused by a nil, you can call `Mocktail.explain_nils` for an
777
- array containing `UnsatisfyingCallExplanation` objects (one for each call to
778
- a faked method that did not satisfy any configured stubbings).
779
-
780
- The returned explanation objects will include both a `reference` object to
781
- explore as well a summary `message`:
782
-
783
- ```ruby
784
- def prep
785
- ice = ice_tray.fill(:tap_water, 50)
786
- puts Mocktail.explain_nils.first.message
787
- glass.add(ice)
788
- end
789
- ```
790
-
791
- Which will print:
792
-
793
- ```
794
- This `nil' was returned by a mocked `IceTray#fill' method
795
- because none of its configured stubbings were satisfied.
796
-
797
- The actual call:
798
-
799
- fill(:tap_water, 50)
800
-
801
- The call site:
802
-
803
- /path/to/your/code.rb:42:in `prep'
804
-
805
- Stubbings configured prior to this call but not satisfied by it:
806
-
807
- fill(:tap_water, 30)
808
- ```
809
-
810
- The `reference` object will have details of the `call` itself, an array of
811
- `other_stubbings` defined on the faked method, and a `backtrace` to determine
812
- which call site produced the unexpected `nil` value.
813
-
814
- ### Mocktail.calls
815
-
816
- When practicing test-driven development, you may want to ensure that a
817
- dependency wasn't called at all. To provide a terse way to express this,
818
- Mocktail offers a top-level `calls(double, method_name = nil)` method that
819
- returns an array of the calls to the mock (optionally filtered to a
820
- particular method name) in the order they were called.
821
-
822
- Suppose you were writing a test of this method for example:
823
-
824
- ```ruby
825
- def import_users
826
- users_response = @gets_users.get
827
- if users_response.success?
828
- @upserts_users.upsert(users_response.data)
829
- end
830
- end
831
- ```
832
-
833
- A test case of the negative branch of that `if` statement (when `success?` is
834
- false) might simply want to assert that `@upserts_users.upsert` wasn't called at
835
- all, regardless of its parameters.
836
-
837
- The easiest way to do this is to use `Mocktail.calls()` method, which is an
838
- alias of [Mocktail.explain(double).reference.calls](#mocktailexplain) that can
839
- filter to a specific method name. In the case of a test of the above method, you
840
- could assert:
841
-
842
- ```ruby
843
- # Assert that the `upsert` method on the mock was never called
844
- assert_equal 0, Mocktail.calls(@upserts_users, :upsert).size
845
-
846
- # Assert that NO METHODS on the mock were called at all:
847
- assert_equal 0, Mocktail.calls(@upserts_users).size
848
- ```
849
-
850
- If you're interested in doing more complicated introspection in the nature of
851
- the calls, their ordering, and so forth, the `calls` method will return
852
- `Mocktail::Call` values with the args, kwargs, block, and information about the
853
- original class and method being mocked.
854
-
855
- (While this behavior can technically be accomplished with `verify(times: 0) { …
856
- }`, it's verbose and error prone to do so. Because `verify` is careful to only
857
- assert exact argument matches, it can get pretty confusing to remember to tack
858
- on `ignore_extra_args: true` and to call the method with zero args to cover all
859
- cases.)
860
-
861
- ### Mocktail.reset
862
-
863
- This one's simple: you probably want to call `Mocktail.reset` after each test,
864
- but you _definitely_ want to call it if you're using `Mocktail.replace` or
865
- `Mocktail.of_next` anywhere, since those will affect state that is shared across
866
- tests.
867
-
868
- Calling reset in a `teardown` or `after(:each)` hook will also improve the
869
- usefulness of messages returned by `Mocktail.explain` and
870
- `Mocktail.explain_nils`.
871
-
872
- ## References
873
-
874
- Mocktail is designed following a somewhat academic understanding of what mocking
875
- is and how it should be used. Below are several references on this topic.
876
-
877
- Blog Posts and Papers:
878
-
879
- - [Endo-Testing: Unit Testing with Mock
880
- Objects](<https://www2.ccs.neu.edu/research/demeter/related-work/extreme-programming/MockObjectsFinal.PDF>
881
- by Tim Mackinnon, Steve Freeman, and Philip Craig, the paper that introduced
882
- mocking presented by the creators of mocking.
883
- - Michael Feathers' [The Flawed Theory Behind Unit
884
- Testing](<https://michaelfeathers.typepad.com/michael_feathers_blog/2008/06/the-flawed-theo.html>)
885
-
886
- Books:
887
-
888
- - [_Growing Object-Oriented Software, Guided by
889
- Tests_](<https://bookshop.org/books/growing-object-oriented-software-guided-by-tests/9780321503626>)
890
- by Steve Freeman and Nat Price
891
-
892
- Talks:
893
-
894
- - [Please don’t mock me](https://www.youtube.com/watch?v=Af4M8GMoxi4) by Justin
895
- Searls
896
-
897
- ## Acknowledgements
898
-
899
- Mocktail is created & maintained by the software agency [Test
900
- Double](https://testdouble.com). If you've ever come across our eponymously-named
901
- [testdouble.js](https://github.com/testdouble/testdouble.js/), you might find
902
- Mocktail's API to be quite similar. The term "test double" was originally coined
903
- by Gerard Meszaros in his book [xUnit Test
904
- Patterns](http://xunitpatterns.com/Test%20Double.html).
905
-
906
- The name is inspired by the innovative Java mocking library
907
- [Mockito](https://site.mockito.org). Mocktail also the spiritual successor to
908
- [gimme](https://github.com/searls/gimme), which offers a similar API but which
909
- fell victim to the limitations of Ruby 1.8.7 (and
910
- [@searls](https://twitter.com/searls)'s Ruby chops). Gimme was also one of the
911
- final projects we collaborated with [Jim Weirich](https://github.com/jimweirich)
912
- on, so this approach to isolated unit testing holds a special significance to
913
- us.
914
-
915
- ## Code of Conduct
916
-
917
- This project follows Test Double's [code of
918
- conduct](https://testdouble.com/code-of-conduct) for all community interactions,
919
- including (but not limited to) one-on-one communications, public posts/comments,
920
- code reviews, pull requests, and GitHub issues. If violations occur, Test Double
921
- will take any action they deem appropriate for the infraction, up to and
922
- including blocking a user from the organization's repositories.
1
+ # Mocktail
2
+
3
+ Mocktail is a mocking library for Ruby built with modern Ruby 3 APIs and the
4
+ _only one_ with first-class support for type checking with
5
+ [Sorbet](https://sorbet.org). Mocktail was created to accelerate test-driven
6
+ development in Ruby and is designed to prevent common problems that lead to
7
+ brittle and confusing tests.
8
+
9
+ Your first choice is a consequential one: **how do you want your Mocktail?**
10
+
11
+ <p align="center" width="100%">
12
+ <a href="/docs/installation_untyped.md">
13
+ <img src="docs/img/mocktail_untyped.jpg" width="45%" alt="Try Mocktail without type checking">
14
+ </a>
15
+ <a href="/docs/installation_sorbet.md">
16
+ <img src="docs/img/mocktail_sorbet.jpg" width="45%" alt="Try Mocktail with Sorbet type checking">
17
+ </a>
18
+ </p>