README.md in mocktail-1.2.3 vs README.md in mocktail-2.0.0

- old
+ new

@@ -1,922 +1,18 @@ -<img -src="https://user-images.githubusercontent.com/79303/134366631-9c6cfe67-a9c0-4096-bbea-ba1698a85b0b.png" -width="90%"/> +# Mocktail -# Mocktail 🍸 +Mocktail is a mocking library for Ruby built with modern Ruby 3 APIs and the +_only one_ with first-class support for type checking with +[Sorbet](https://sorbet.org). Mocktail was created to accelerate test-driven +development in Ruby and is designed to prevent common problems that lead to +brittle and confusing tests. -Mocktail is a [test -double](https://github.com/testdouble/contributing-tests/wiki/Test-Double) -library for Ruby that provides a terse and robust API for creating mocks, -getting them in the hands of the code you're testing, stub & verify behavior, -and even safely override class methods. +Your first choice is a consequential one: **how do you want your Mocktail?** -If you'd prefer a voice & video introduction to Mocktail aside from this README, -you might enjoy this ⚡️[Lightning -Talk](https://blog.testdouble.com/talks/2022-05-18-please-mock-me?utm_source=twitter&utm_medium=organic-social&utm_campaign=conf-talk)⚡️ -from RailsConf 2022. - -## An aperitif - -Before getting into the details, let's demonstrate what Mocktail's API looks -like. Suppose you want to test a `Bartender` class: - -```ruby -class Bartender - def initialize - @shaker = Shaker.new - @glass = Glass.new - @bar = Bar.new - end - - def make_drink(name, customer:) - if name == :negroni - drink = @shaker.combine(:gin, :campari, :sweet_vermouth) - @glass.pour!(drink) - @bar.pass(@glass, to: customer) - end - end -end -``` - -You could write an isolated unit test with Mocktail like this: - -```ruby -shaker = Mocktail.of_next(Shaker) -glass = Mocktail.of_next(Glass) -bar = Mocktail.of_next(Bar) -subject = Bartender.new -stubs { shaker.combine(:gin, :campari, :sweet_vermouth) }.with { :a_drink } -stubs { bar.pass(glass, to: "Eileen") }.with { "🎉" } - -result = subject.make_drink(:negroni, customer: "Eileen") - -assert_equal "🎉", result -# Oh yeah, and make sure the drink got poured! Silly side effects! -verify { glass.pour!(:a_drink) } -``` - -## Why Mocktail? - -Besides helping you avoid a hangover, Mocktail offers several advantages over -Ruby's other mocking libraries: - -* **Simpler test recipes**: [Mocktail.of_next(type)](#mocktailof_next) both - creates your mock and supplies to your subject under test in a single - one-liner. No more forcing dependency injection for the sake of your tests -* **WYSIWYG API**: Want to know how to stub a call to `phone.dial(911)`? You - just demonstrate the call with `stubs { phone.dial(911) }.with { :operator }`. - Because stubbing & verifying looks just like the actual call, your tests - becomes a sounding board for your APIs as you invent them -* **Argument validation**: Ever see a test pass after a change to a mocked - method should have broken it? Not with Mocktail, you haven't -* **Mocked class methods**: Singleton methods on modules and classes can be - faked out using [`Mocktail.replace(type)`](#mocktailreplace) without - sacrificing thread safety -* **Super-duper detailed error messages** A good mocking library should make - coding feel like - [paint-by-number](https://en.wikipedia.org/wiki/Paint_by_number), thoughtfully - guiding you from one step to the next. Calling a method that doesn't exist - will print a sample definition you can copy-paste. Verification failures will - print every call that _did_ occur. And [Mocktail.explain()](#mocktailexplain) - provides even more introspection -* **Expressive**: Built-in [argument matchers](#mocktailmatchers) and a simple - API for adding [custom matchers](#custom-matchers) allow you to tune your - stubbing configuration and call verification to match _exactly_ what your test - intends -* **Powerful**: [Argument captors](#mocktailcaptor) for assertions of very - complex arguments, as well as advanced configuration options for stubbing & - verification - -## Ready to order? - -### Install the gem - -The main ingredient to add to your Gemfile: - -```ruby -gem "mocktail", group: :test -``` - -### Sprinkle in the DSL - -Then, in each of your tests or in a test helper, you'll probably want to include -Mocktail's DSL. (This is optional, however, as every method in the DSL is also -available as a singleton method on `Mocktail`.) - -In Minitest, you might add the DSL with: - -```ruby -class Minitest::Test - include Mocktail::DSL -end -``` - -Or, in RSpec: - -```ruby -RSpec.configure do |config| - config.include Mocktail::DSL -end -``` - -### Clean up when you're done - -To reset Mocktail's internal state between tests and avoid test pollution, you -should also call `Mocktail.reset` after each test: - -In Minitest: - -```ruby -class Minitest::Test - # Or, if in a Rails test, in a `teardown do…end` block - def teardown - Mocktail.reset - end -end -``` - -And RSpec: - -```ruby -RSpec.configure do |config| - config.after(:each) do - Mocktail.reset - end -end -``` - -## API - -The entire public API is listed in the [top-level module's -source](lib/mocktail.rb). Below is a longer menu to explain what goes into each -feature. - -### Mocktail.of - -`Mocktail.of(module_or_class)` takes a module or class and returns an instance -of an object with fake methods in place of all its instance methods which can -then be stubbed or verified. - -```ruby -class Clothes; end; -class Shoe < Clothes - def tie(laces) - end -end - -shoe = Mocktail.of(Shoe) -shoe.instance_of?(Shoe) # => true -shoe.is_a?(Clothes) # => true -shoe.class == Shoe # => false! -shoe.to_s # => #<Mocktail of Shoe:0x00000001343b57b0>" -``` - -### Mocktail.of_next - -`Mocktail.of_next(klass, [count: 1])` takes a class and returns one mock (the -default) or an array of multiple mocks. It also effectively overrides the -behavior of that class's constructor to return those mock(s) in order and -finally restoring its previous behavior. - -For example, if you wanted to test the `Notifier` class below: - -```ruby -class Notifier - def initialize - @mailer = Mailer.new - end - - def notify(name) - @mailer.deliver!("Hello, #{name}") - end -end -``` - -You could write a test like this: - -```ruby -def test_notifier - mailer = Mocktail.of_next(Mailer) - subject = Notifier.new - - subject.notify("Pants") - - verify { mailer.deliver!("Hello, Pants") } -end -``` - -There's nothing wrong with creating mocks using `Mocktail.of` and passing them -to your subject some other way, but this approach allows you to write very terse -isolation tests without foisting additional indirection or dependency injection -in for your tests' sake. - -### Mocktail.stubs - -Configuring a fake method to take a certain action or return a particular value -is called "stubbing". To stub a call with a value, you can call `Mocktail.stubs` -(or just `stubs` if you've included `Mocktail::DSL`) and then specify an effect -that will be invoked whenever that call configuration is satisfied using `with`. - -The API is very simple in the simple case: - -```ruby -class UserRepository - def find(id, debug: false); end - - def transaction(&blk); end -end -``` - -You could stub responses to a mock of the `UserRepository` like this: - -```ruby -user_repository = Mocktail.of(UserRepository) - -stubs { user_repository.find(42) }.with { :a_user } -user_repository.find(42) # => :a_user -user_repository.find(43) # => nil -user_repository.find # => ArgumentError: wrong number of arguments (given 0, expected 1) -``` - -The block passed to `stubs` is called the "demonstration", because it represents -an example of the kind of calls that Mocktail should match. - -If you want to get fancy, you can use matchers to make your demonstration more -dynamic. For example, you could match any number with: - -```ruby -stubs { |m| user_repository.find(m.numeric) }.with { :another_user } -user_repository.find(41) # => :another_user -user_repository.find(42) # => :another_user -user_repository.find(43) # => :another_user -``` - -Stubbings are last-in-wins, which is why the stubbing above would have -overridden the earlier-but-more-specific stubbing of `find(42)`. - -A stubbing's effect can also be changed dynamically based on the actual call -that satisfied the demonstration by looking at the `call` block argument: - -```ruby -stubs { |m| user_repository.find(m.is_a(Integer)) }.with { |call| - {id: call.args.first} -} -user_repository.find(41) # => {id: 41} -# Since 42.5 is a Float, the earlier stubbing will win here: -user_repository.find(42.5) # => :another_user -user_repository.find(43) # => {id: 43} -``` - -It's certainly more complex to think through, but if your stubbed method takes a -block, your demonstration can pass a block of its own and inspect or invoke it: - -```ruby -stubs { - user_repository.transaction { |block| block.call == {id: 41} } -}.with { :successful_transaction } - -user_repository.transaction { - user_repository.find(41) -} # => :successful_transaction -user_repository.transaction { - user_repository.find(40) -} # => nil -``` - -There are also several advanced options you can pass to `stubs` to control its -behavior. - -`times` will limit the number of times a satisfied stubbing can have its effect: - -```ruby -stubs { |m| user_repository.find(m.any) }.with { :not_found } -stubs(times: 2) { |m| user_repository.find(1) }.with { :someone } - -user_repository.find(1) # => :someone -user_repository.find(1) # => :someone -user_repository.find(1) # => :not_found -``` - -`ignore_extra_args` will allow a demonstration to be considered satisfied even -if it fails to specify arguments and keyword arguments made by the actual call: - -```ruby -stubs { user_repository.find(4) }.with { :a_person } -user_repository.find(4, debug: true) # => nil - -stubs(ignore_extra_args: true) { user_repository.find(4) }.with { :b_person } -user_repository.find(4, debug: true) # => :b_person -``` - -And `ignore_block` will similarly allow a demonstration to not concern itself -with whether an actual call passed the method a block—it's satisfied either way: - -```ruby -stubs { user_repository.transaction }.with { :transaction } -user_repository.transaction {} # => nil - -stubs(ignore_block: true) { user_repository.transaction }.with { :transaction } -user_repository.transaction {} # => :transaction -``` - -### Mocktail.verify - -In practice, we've found that we stub far more responses than we explicitly -verify a particular call took place. That's because our code normally returns -some observable value that is _influenced_ by our dependencies' behavior, so -adding additional assertions that they be called would be redundant. That -said, for cases where a dependency doesn't return a value but just has a -necessary side effect, the `verify` method exists (and like `stubs` is included -in `Mocktail::DSL`). - -Once you've gotten the hang of stubbing, you'll find that the `verify` method is -intentionally very similar. They almost rhyme. - -For this example, consider an `Auditor` class that our code might need to call -to record that certain actions took place. - -```ruby -class Auditor - def record!(message, user_id:, action: nil); end -end -``` - -Once you've created a mock of the `Auditor`, you can start verifying basic -calls: - -```ruby -auditor = Mocktail.of(Auditor) - -verify { auditor.record!("hello", user_id: 42) } -# => raised Mocktail::VerificationError -# Expected mocktail of Auditor#record! to be called like: -# -# record!("hello", user_id: 42) -# -# But it was never called. -``` - -Wups! Verify will blow up whenever a matching call hasn't occurred, so it -should be called after you've invoked your subject under test along with any -other assertions you have. - -If we make a call that satisfies the `verify` call's demonstration, however, you -won't see that error: - -```ruby -auditor.record!("hello", user_id: 42) - -verify { auditor.record!("hello", user_id: 42) } # => nil -``` - -There, nothing happened! Just like any other assertion library, you only hear -from `verify` when verification fails. - -Just like with `stubs`, you can any built-in or custom matchers can serve as -garnishes for your demonstration: - -```ruby -auditor.record!("hello", user_id: 42) - -verify { |m| auditor.record!(m.is_a(String), user_id: m.numeric) } # => nil -# But this will raise a VerificationError: -verify { |m| auditor.record!(m.is_a(String), user_id: m.that { |arg| arg > 50}) } -``` - -When you pass a block to your demonstration, it will be invoked with any block -that was passed to the actual call to the mock. Truthy responses will satisfy -the verification and falsey ones will fail: - -```ruby -auditor.record!("ok", user_id: 1) { Time.new } - -verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Time) } } # => nil -# But this will raise a VerificationError: -verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Date) } } -``` - -`verify` supports the same options as `stubs`: - -* `times` will require the demonstrated call happened exactly `times` times (by - default, the call has to happen 1 or more times) -* `ignore_extra_args` will allow the demonstration to forego specifying optional - arguments while still being considered satisfied -* `ignore_block` will similarly allow the demonstration to forego specifying a - block, even if the actual call receives one - -Note that if you want to verify a method _wasn't_ called at all or called a -specific number of times—especially if you don't care about the parameters, you -may want to look at the [Mocktail.calls()](#mocktailcalls) API. - -### Mocktail.matchers - -You'll probably never need to call `Mocktail.matchers` directly, because it's -the object that is passed to every demonstration block passed to `stubs` and -`verify`. By default, a stubbing (e.g. `stubs { email.send("text") }`) is only -considered satisfied if every argument passed to an actual call was passed an -`==` check. Matchers allow us to relax or change that constraint for both -regular arguments and keyword arguments so that our demonstrations can match -more kinds of method invocations. - -Matchers allow you to specify stubbings and verifications that look like this: - -```ruby -stubs { |m| email.send(m.is_a(String)) }.with { "I'm an email" } -``` - -#### Built-in matchers - -These matchers come out of the box: - -* `any` - Will match any value (even nil) in the given argument position or - keyword -* `is_a(type)` - Will match when its `type` passes an `is_a?` check against the - actual argument -* `includes(thing, [**more_things])` - Will match when all of its arguments are - contained by the corresponding argument—be it a string, array, hash, or - anything that responds to `includes?` -* `matches(pattern)` - Will match when the provided string or pattern passes - a `match?` test on the corresponding argument; usually used to match strings - that contain a particular substring or pattern, but will work with any - argument that responds to `match?` -* `not(thing)` - Will only match when its argument _does not_ equal (via `!=`) - the actual argument -* `numeric` - Will match when the actual argument is an instance of `Integer`, - `Float`, or (if loaded) `BigDecimal` -* `that { |arg| … }` - Takes a block that will receive the actual argument. If - the block returns truthy, it's considered a match; otherwise, it's not a - match. - -#### Custom matchers - -If you want to write your own matchers, check out [the source for -examples](lib/mocktail/matchers/includes.rb). Once you've implemented a class, -just pass it to `Mocktail.register_matcher` in your test helper. - -```ruby -class MyAwesomeMatcher < Mocktail::Matchers::Base - def self.matcher_name - :awesome - end - - def match?(actual) - "#{@expected}✨" == actual - end -end - -Mocktail.register_matcher(MyAwesomeMatcher) -``` - -Then, a stubbing like this: - -```ruby -stubs { |m| user_repository.find(m.awesome(11)) }.with { :awesome_user } - -user_repository.find("11")) # => nil -user_repository.find("11✨")) # => :awesome_user -``` - -### Mocktail.captor - -An argument captor is a special kind of matcher… really, it's a matcher factory. -Suppose you have a `verify` call for which one of the expected arguments is -_really_ complicated. Since `verify` tends to be paired with fire-and-forget -APIs that are being invoked for the side effect, this is a pretty common case. -You want to be able to effectively snag that value and then run any number of -specific assertions against it. - -That's what `Mocktail.captor` is for. It's easiest to make sense of this by -example. Given this `BigApi` class that's presumably being called by your -subject at the end of a lot of other work building up a payload: - -```ruby -class BigApi - def send(payload); end -end -``` - -You could capture the value of that payload as part of the verification of the -call: - -```ruby -big_api = Mocktail.of(BigApi) - -big_api.send({imagine: "that", this: "is", a: "huge", object: "!"}) - -payload_captor = Mocktail.captor -verify { big_api.send(payload_captor.capture) } # => nil! -``` - -The `verify` above will pass because _a_ call did happen, but we haven't -asserted anything beyond that yet. What really happened is that -`payload_captor.capture` actually returned a matcher that will return true for -any argument _while also sneakily storing a copy of the argument value_. - -That's why we instantiated `payload_captor` with `Mocktail.captor` outside the -demonstration block, so we can inspect its `value` after the `verify` call: - -```ruby -payload_captor = Mocktail.captor -verify { big_api.send(payload_captor.capture) } # => nil! - -payload = payload_captor.value # {:imagine=>"that", :this=>"is", :a=>"huge", :object=>"!"} -assert_equal "huge", payload[:a] -``` - -### Mocktail.replace - -Mocktail was written to support isolated test-driven development, which usually -results in a lot of boring classes and instance methods. But sometimes you need -to mock singleton methods on classes or modules, and we support that too. - -When you call `Mocktail.replace(type)`, all of the singleton methods on the -provided type are replaced with fake methods available for stubbing and -verification. It's really that simple. - -For example, if our `Bartender` class has a class method: - -```ruby -class Bartender - def self.cliche_greeting - ["It's 5 o'clock somewhere!", "Norm!"].sample - end -end -``` - -We can replace the behavior of the overall class, and then stub how we'd like it -to respond, in our test: - -```ruby -Mocktail.replace(Bartender) -stubs { Bartender.cliche_greeting }.with { "Norm!" } -``` - -[**Obligatory warning:** Mocktail does its best to ensure that other threads -won't be affected when you replace the singleton methods on a type, but your -mileage may very! Singleton methods are global and code that introspects or -invokes a replaced method in a peculiar-enough way could lead to hard-to-track -down bugs. (If this concerns you, then the fact that class methods are -effectively global state may be a great reason not to rely too heavily on -them!)] - -### Mocktail.explain - -Test debugging is hard enough when there _aren't_ fake objects flying every -which way, so Mocktail tries to make it a little easier on you. In addition to -returning useful messages throughout the API, the gem also includes an -introspection method `Mocktail.explain(thing)`, which returns a human-readable -`message` and a `reference` object with useful attributes (that vary depending -on the type of fake `thing` you pass in. Below are some things `explain()` can -do. - -#### Fake instances created by Mocktail - -Any instances created by `Mocktail.of` or `Mocktail.of_next` can be passed to -`Mocktail.explain`, and they will list out all the calls and stubbings made for -each of their fake methods. - -Suppose these interactions have occurred: - -```ruby -ice_tray = Mocktail.of(IceTray) - -Mocktail.stubs { ice_tray.fill(:tap_water, 30) }.with { :some_ice } - -ice_tray.fill(:tap_water, 50) -``` - -You can interrogate what's going on with the fake instance by passing it to -`explain`: - -```ruby -explanation = Mocktail.explain(ice_tray) - -explanation.reference.type #=> IceTray -explanation.reference.double #=> The ice_tray instance -explanation.reference.calls #=> details on each invocation of each method -explanation.reference.stubbings #=> all stubbings configured for each method -``` - -Calling `explanation.message` will return: - -``` -This is a fake `IceTray' instance. - -It has these mocked methods: - - fill - -`IceTray#fill' stubbings: - - fill(:tap_water, 30) - -`IceTray#fill' calls: - - fill(:tap_water, 50) - -``` - -#### Modules and classes with singleton methods replaced - -If you've called `Mocktail.replace()` on a class or module, it can also be -passed to `Mocktail.explain()` for a summary of all the stubbing configurations -and calls made against its faked singleton methods for the currently running -thread. - -Imagine a `Shop` class with `self.open!` and `self.close!` singleton methods: - -```ruby -Mocktail.replace(Shop) - -stubs { |m| Shop.open!(m.numeric) }.with { :a_bar } - -Shop.open!(42) - -Shop.close!(42) - -explanation = Mocktail.explain(Shop) - -explanation.reference.type #=> Shop -explanation.reference.replaced_method_names #=> [:close!, :open!] -explanation.reference.calls #=> details on each invocation of each method -explanation.reference.stubbings #=> all stubbings configured for each method -``` - -And `explanation.message` will return: - -```ruby -`Shop' is a class that has had its singleton methods faked. - -It has these mocked methods: - - close! - - open! - -`Shop.close!' has no stubbings. - -`Shop.close!' calls: - - close!(42) - - close!(42) - -`Shop.open!' stubbings: - - open!(numeric) - - open!(numeric) - -`Shop.open!' calls: - - open!(42) - - open!(42) -``` - -#### Methods on faked instances and replaced types - -In addition to passing the test double, you can also pass a reference to any -fake method created by Mocktail to `Mocktail.explain`: - -```ruby -ice_tray = Mocktail.of(IceTray) - -ice_tray.fill(:chilled, 50) - -explanation = Mocktail.explain(ice_tray.method(:fill)) - -explanation.reference.receiver #=> a reference to the `ice_tray` instance -explanation.reference.calls #=> details on each invocation of the method -explanation.reference.stubbings #=> all stubbings configured for the method -``` - -The above may be handy in cases where you want to assert the number of calls of -a method outside the `Mocktail.verify` API: - -```ruby -assert_equal 1, explanation.reference.calls.size -``` - -The explanation will also contain a `message` like this: - -``` -`IceTray#fill' has no stubbings. - -`IceTray#fill' calls: - - fill(:chilled, 50) -``` - -Replaced singleton methods can also be passed to `explain()`, so something like -`Mocktail.explain(Shop.method(:open!))` from the earlier example would also work -(with `Shop` being the `receiver` on the explanation's `reference`). - -#### Undefined methods - -There's no API for this one, but Mocktail also offers explanations for methods -that don't exist yet. You'll see this error message whenever you try to call a -method that doesn't exist on a test double. The message is designed to -facilitate "paint-by-numbers" TDD, by including a sample definition of the -method you had attempted to call that can be copy-pasted into a source listing: - -```ruby -class IceTray -end - -ice_tray = Mocktail.of(IceTray) - -ice_tray.fill(:water_type, 30) -# => No method `IceTray#fill' exists for call: (NoMethodError) -# -# fill(:water_type, 30) -# -# Need to define the method? Here's a sample definition: -# -# def fill(water_type, arg) -# end -``` - -From there, you can just copy-paste the provided method stub as a starting point -for your new method: - -```ruby -class IceTray - def fill(water_type, amount) - end -end -``` - -### Mocktail.explain_nils - -Is a faked method returning `nil` and you don't understand why? - -By default, methods faked by Mocktail will return `nil` when no stubbing is -satisfied. A frequent frustration, therefore, is when the way `stubs {}.with {}` -is configured does not satisfy a call the way you expected. To try to make -debugging this a little bit easier, the gem provides a top-level -`Mocktail.explain_nils` method that will return an array of summaries of every -call to a faked method that failed to satisfy any stubbings. - -For example, suppose you stub this `fill` method like so: - -```ruby -ice_tray = Mocktail.of(IceTray) - -stubs { ice_tray.fill(:tap_water, 30) }.with { :normal_ice } -``` - -But then you find that your subject under test is just getting `nil` back and -you don't understand why: - -```ruby -def prep - ice = ice_tray.fill(:tap_water, 50) - glass.add(ice) # => why is `ice` nil?! -end -``` - -Whenever you're confused by a nil, you can call `Mocktail.explain_nils` for an -array containing `UnsatisfyingCallExplanation` objects (one for each call to -a faked method that did not satisfy any configured stubbings). - -The returned explanation objects will include both a `reference` object to -explore as well a summary `message`: - -```ruby -def prep - ice = ice_tray.fill(:tap_water, 50) - puts Mocktail.explain_nils.first.message - glass.add(ice) -end -``` - -Which will print: - -``` -This `nil' was returned by a mocked `IceTray#fill' method -because none of its configured stubbings were satisfied. - -The actual call: - - fill(:tap_water, 50) - -The call site: - - /path/to/your/code.rb:42:in `prep' - -Stubbings configured prior to this call but not satisfied by it: - - fill(:tap_water, 30) -``` - -The `reference` object will have details of the `call` itself, an array of -`other_stubbings` defined on the faked method, and a `backtrace` to determine -which call site produced the unexpected `nil` value. - -### Mocktail.calls - -When practicing test-driven development, you may want to ensure that a -dependency wasn't called at all. To provide a terse way to express this, -Mocktail offers a top-level `calls(double, method_name = nil)` method that -returns an array of the calls to the mock (optionally filtered to a -particular method name) in the order they were called. - -Suppose you were writing a test of this method for example: - -```ruby -def import_users - users_response = @gets_users.get - if users_response.success? - @upserts_users.upsert(users_response.data) - end -end -``` - -A test case of the negative branch of that `if` statement (when `success?` is -false) might simply want to assert that `@upserts_users.upsert` wasn't called at -all, regardless of its parameters. - -The easiest way to do this is to use `Mocktail.calls()` method, which is an -alias of [Mocktail.explain(double).reference.calls](#mocktailexplain) that can -filter to a specific method name. In the case of a test of the above method, you -could assert: - -```ruby -# Assert that the `upsert` method on the mock was never called -assert_equal 0, Mocktail.calls(@upserts_users, :upsert).size - -# Assert that NO METHODS on the mock were called at all: -assert_equal 0, Mocktail.calls(@upserts_users).size -``` - -If you're interested in doing more complicated introspection in the nature of -the calls, their ordering, and so forth, the `calls` method will return -`Mocktail::Call` values with the args, kwargs, block, and information about the -original class and method being mocked. - -(While this behavior can technically be accomplished with `verify(times: 0) { … -}`, it's verbose and error prone to do so. Because `verify` is careful to only -assert exact argument matches, it can get pretty confusing to remember to tack -on `ignore_extra_args: true` and to call the method with zero args to cover all -cases.) - -### Mocktail.reset - -This one's simple: you probably want to call `Mocktail.reset` after each test, -but you _definitely_ want to call it if you're using `Mocktail.replace` or -`Mocktail.of_next` anywhere, since those will affect state that is shared across -tests. - -Calling reset in a `teardown` or `after(:each)` hook will also improve the -usefulness of messages returned by `Mocktail.explain` and -`Mocktail.explain_nils`. - -## References - -Mocktail is designed following a somewhat academic understanding of what mocking -is and how it should be used. Below are several references on this topic. - -Blog Posts and Papers: - -- [Endo-Testing: Unit Testing with Mock - Objects](<https://www2.ccs.neu.edu/research/demeter/related-work/extreme-programming/MockObjectsFinal.PDF> - by Tim Mackinnon, Steve Freeman, and Philip Craig, the paper that introduced - mocking presented by the creators of mocking. -- Michael Feathers' [The Flawed Theory Behind Unit - Testing](<https://michaelfeathers.typepad.com/michael_feathers_blog/2008/06/the-flawed-theo.html>) - -Books: - -- [_Growing Object-Oriented Software, Guided by - Tests_](<https://bookshop.org/books/growing-object-oriented-software-guided-by-tests/9780321503626>) - by Steve Freeman and Nat Price - -Talks: - -- [Please don’t mock me](https://www.youtube.com/watch?v=Af4M8GMoxi4) by Justin - Searls - -## Acknowledgements - -Mocktail is created & maintained by the software agency [Test -Double](https://testdouble.com). If you've ever come across our eponymously-named -[testdouble.js](https://github.com/testdouble/testdouble.js/), you might find -Mocktail's API to be quite similar. The term "test double" was originally coined -by Gerard Meszaros in his book [xUnit Test -Patterns](http://xunitpatterns.com/Test%20Double.html). - -The name is inspired by the innovative Java mocking library -[Mockito](https://site.mockito.org). Mocktail also the spiritual successor to -[gimme](https://github.com/searls/gimme), which offers a similar API but which -fell victim to the limitations of Ruby 1.8.7 (and -[@searls](https://twitter.com/searls)'s Ruby chops). Gimme was also one of the -final projects we collaborated with [Jim Weirich](https://github.com/jimweirich) -on, so this approach to isolated unit testing holds a special significance to -us. - -## Code of Conduct - -This project follows Test Double's [code of -conduct](https://testdouble.com/code-of-conduct) for all community interactions, -including (but not limited to) one-on-one communications, public posts/comments, -code reviews, pull requests, and GitHub issues. If violations occur, Test Double -will take any action they deem appropriate for the infraction, up to and -including blocking a user from the organization's repositories. +<p align="center" width="100%"> + <a href="/docs/installation_untyped.md"> + <img src="docs/img/mocktail_untyped.jpg" width="45%" alt="Try Mocktail without type checking"> + </a> + <a href="/docs/installation_sorbet.md"> + <img src="docs/img/mocktail_sorbet.jpg" width="45%" alt="Try Mocktail with Sorbet type checking"> + </a> +</p>