mocktail 1.2.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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>