From 00e184db5d776f001d30df2f17a8881eb4205c8e Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Wed, 12 Aug 2026 10:41:45 -0400 Subject: [PATCH] Mockery to Double --- mocking.md | 99 +++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/mocking.md b/mocking.md index 2709b3a..56e26da 100644 --- a/mocking.md +++ b/mocking.md @@ -5,42 +5,49 @@ description: When testing your applications, you may wish to "mock" specific cla # Mocking -> **Requirements:** [Mockery 1.0+](https://github.com/mockery/mockery/) - When testing your applications, you may wish to "mock" specific classes to prevent them from actually being invoked during a particular test. For instance, if your application interacts with an API that initiates a payment, you likely want to "mock" the API client locally so that the actual payment is never made. -Before getting started, you will need to install a mocking library. We recommend [Mockery](https://github.com/mockery/mockery/), but you are free to choose any other library that suits your needs. +Before getting started, you will need to install a mocking library. We recommend [Double](https://github.com/jasonmccreary/double), but you are free to choose any other library that suits your needs. -To get started, you may install Mockery using the Composer package manager: +To get started, you may install Double using the Composer package manager: ```bash -composer require mockery/mockery --dev +composer require jasonmccreary/double --dev +``` + +You should also register Double's `VerifiesDoubles` trait in your `Pest.php` configuration file, so that every expectation you set is verified automatically at the end of each test: + +```php +// tests/Pest.php +use JMac\Testing\Integrations\PHPUnit\VerifiesDoubles; + +pest()->use(VerifiesDoubles::class); ``` -Comprehensive documentation for Mockery can be found on the [Mockery website](https://docs.mockery.io); this section will focus on the most common use cases for mocking. +Comprehensive documentation for Double can be found on the [Double website](https://testdoublephp.com); this section will focus on the most common use cases for mocking. ## Method Expectations -Mock objects are essential for isolating the code under test and simulating specific behaviors or conditions from other parts of your application. Once you have created a mock using the `Mockery::mock()` method, you may indicate that you expect a certain method to be invoked by calling the `shouldReceive()` method: +Doubles are essential for isolating the code under test and simulating specific behaviors or conditions from other parts of your application. Once you have created a double using the `Double::for()` method, you may indicate that you expect a certain method to be invoked by calling the `expects()` method: ```php use App\Repositories\BookRepository; -use Mockery; +use JMac\Testing\Double; it('may buy a book', function () { - $client = Mockery::mock(PaymentClient::class); - $client->shouldReceive('post'); + $client = Double::for(PaymentClient::class); + $client->expects('post'); $books = new BookRepository($client); $books->buy(); // The API is not actually invoked since `$client->post()` has been mocked... }); ``` -You may mock multiple method calls using the same syntax shown above: +You may double multiple method calls using the same syntax shown above: ```php -$client->shouldReceive('post'); -$client->shouldReceive('delete'); +$client->expects('post'); +$client->expects('delete'); ``` ## Argument Expectations @@ -48,67 +55,67 @@ $client->shouldReceive('delete'); To make your expectations for a method more specific, you may use constraints to limit the expected argument list for a method call. This is accomplished with the `with()` method, as demonstrated in the following example: ```php -$client->shouldReceive('post') +$client->expects('post') ->with($firstArgument, $secondArgument); ``` -To increase the flexibility of argument matching, Mockery provides built-in matcher classes that may be used in place of specific values. For example, instead of passing specific values, you may use `Mockery::any()` to match any argument: +To increase the flexibility of argument matching, Double provides built-in matcher classes that may be used in place of specific values. For example, instead of passing specific values, you may use `Argument::any()` to match any argument: ```php -$client->shouldReceive('post') - ->with($firstArgument, Mockery::any()); +use JMac\Testing\Matching\Argument; + +$client->expects('post') + ->with($firstArgument, Argument::any()); ``` -It is important to note that expectations defined using `shouldReceive()` and `with()` only apply when the method is invoked with the exact arguments you expected. Otherwise, Mockery will throw an exception: +It is important to note that expectations defined using `expects()` and `with()` only apply when the method is invoked with the exact arguments you expected. When an expectation isn't met, Double fails the test: ```php -$client->shouldReceive('post')->with(1); +$client->expects('post')->with(1); -$client->post(2); // fails, throws a `NoMatchingExpectationException` +$client->post(2); // fails the test, since `post` was never called with the expected arguments ``` -Sometimes you may wish to match all passed arguments at once using a closure, rather than relying on built-in matchers for each individual argument. The `withArgs()` method accepts a closure that receives all of the arguments passed to the expected method call. As a result, this expectation will only apply to method calls in which the passed arguments cause the closure to evaluate to true: +Double also ships with matchers for looser argument constraints. For example, you may constrain an argument to a particular type using `Argument::type()`, `Argument::contains()`, or to anything satisfying a callback using `Argument::satisfies()`: ```php -$client->shouldReceive('post')->withArgs(function ($arg) { +$client->expects('save')->with(Argument::type(Post::class)); +$client->expects('shuffle')->with(Argument::contains(1)); +$client->expects('find')->with(Argument::satisfies(function ($arg) { return $arg === 1; -}); - -$client->post(1); // passes, matches the expectation -$client->post(2); // fails, throws a `NoMatchingExpectationException` +})); ``` +Double ships with several other matchers for common needs, such as matching a regular expression. You may find the complete list in Double's [argument matching documentation](https://testdoublephp.com/argument-matching). + ## Return Values -When working with mock objects, you may use the `andReturn()` method to tell Mockery what to return from the mocked methods: +When working with doubles, you may use the `returns()` method to tell Double what to return from the doubled methods: ```php -$client->shouldReceive('post')->andReturn('post response'); +$client->expects('post')->returns('post response'); ``` -You may define a sequence of return values by passing multiple values to the `andReturn()` method: +You may define a sequence of return values by passing multiple values to the `returns()` method: ```php -$client->shouldReceive('post')->andReturn(1, 2); +$client->allows('post')->returns(1, 2); $client->post(); // int(1) $client->post(); // int(2) ``` -Sometimes you may need to calculate the return value of a method call based on the arguments passed to it. This is accomplished with the `andReturnUsing()` method, which accepts one or more closures: +Sometimes you may need to calculate the return value of a method call based on the arguments passed to it. This is accomplished with the `resolves()` method, which accepts a closure that receives the call's arguments: ```php -$mock->shouldReceive('post') - ->andReturnUsing( - fn () => 1, - fn () => 2, - ); +$double->expects('post') + ->resolves(fn ($payload) => $payload['status']); ``` -In addition, you may instruct mocked methods to throw exceptions: +In addition, you may instruct doubled methods to throw exceptions: ```php -$client->shouldReceive('post')->andThrow(new Exception); +$client->expects('post')->throws(new Exception); ``` ## Method Call "Count" Expectations @@ -116,24 +123,24 @@ $client->shouldReceive('post')->andThrow(new Exception); Along with specifying expected arguments and return values for method calls, you may also set expectations for how many times a particular method should be invoked: ```php -$mock->shouldReceive('post')->once(); -$mock->shouldReceive('put')->twice(); -$mock->shouldReceive('delete')->times(3); +$double->expects('put')->times(2); +$double->expects('delete')->times(3); // ... ``` -To specify a minimum number of times a method should be called, you may use the `atLeast()` method: +To specify a minimum or maximum number of times a method should be called, you may pass the `minimum` or `maximum` argument to the `times()` method: ```php -$mock->shouldReceive('delete')->atLeast()->times(3); +$double->expects('put')->times(minimum: 3); +$double->expects('delete')->times(maximum: 3); ``` -Alternatively, Mockery's `atMost()` method allows you to specify the maximum number of times a method may be called: +You may pass both `minimum` and `maximum` arguments to the `times()` method to specific a range of times a method may be called: ```php -$mock->shouldReceive('delete')->atMost()->times(3); +$double->expects('save')->times(1, 3); ``` --- -The goal of this section is to give you an introduction to Mockery, the mocking library we prefer. For a more comprehensive understanding, however, we suggest checking out its [official documentation](https://docs.mockery.io). Next, let's explore how snapshot testing lets you assert against large or complex output without writing it all out by hand: [Snapshot Testing](/docs/snapshot-testing) +The goal of this section is to give you an introduction to Double, the mocking library we prefer. For a more comprehensive understanding, however, we suggest checking out its [official documentation](https://testdoublephp.com). Next, let's explore how snapshot testing lets you assert against large or complex output without writing it all out by hand: [Snapshot Testing](/docs/snapshot-testing)