Mocking

Requirements: Mockery 1.0+

When testing your applications, you may want 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 to prevent the actual payment from being made.

Before getting started, you will need to install a mocking library. We recommend Mockery, but you are free to choose any other library that suits your needs.

To begin using Mockery, require it using the Composer package manager.

1composer require mockery/mockery --dev

While comprehensive documentation for Mockery can be found on the Mockery website, this section will discuss the most common use cases for mocking.

Method Expectations

Mock objects are essential for isolating the code being tested and simulating specific behaviors or conditions from other pieces of the application. After creating a mock using the Mockery::mock() method, we can indicate that we expect a certain method to be invoked by calling the shouldReceive() method.

1use App\Repositories\BookRepository;
2use Mockery;
3 
4it('may buy a book', function () {
5 $client = Mockery::mock(PaymentClient::class);
6 $client->shouldReceive('post');
7 
8 $books = new BookRepository($client);
9 $books->buy(); // The API is not actually invoked since `$client->post()` has been mocked...
10});

It is possible to mock multiple method calls using the same syntax shown above.

1$client->shouldReceive('post');
2$client->shouldReceive('delete');

Argument Expectations

In order to make our expectations for a method more specific, we can use constraints to limit the expected argument list for a method call. This can be done by utilizing the with() method, as demonstrated in the following example.

1$client->shouldReceive('post')
2 ->with($firstArgument, $secondArgument);

In order to increase the flexibility of argument matching, Mockery provides built-in matcher classes that can be used in place of specific values. For example, instead of using specific values, we can use Mockery::any() to match any argument.

1$client->shouldReceive('post')
2 ->with($firstArgument, Mockery::any());

It is important to note that expectations defined using shouldReceive() and with() only apply when the method is invoked with the exact arguments that you expected. Otherwise, Mockery will throw an exception.

1$client->shouldReceive('post')->with(1);
2 
3$client->post(2); // fails, throws a `NoMatchingExpectationException`

In certain cases, it may be more appropriate to use a closure to match all passed arguments simultaneously, 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 be applied to method calls in which the passed arguments cause the closure to evaluate to true.

1$client->shouldReceive('post')->withArgs(function ($arg) {
2 return $arg === 1;
3});
4 
5$client->post(1); // passes, matches the expectation
6$client->post(2); // fails, throws a `NoMatchingExpectationException`

Return Values

When working with mock objects, we can use the andReturn() method to tell Mockery what to return from the mocked methods.

1$client->shouldReceive('post')->andReturn('post response');

We can define a sequence of return values by passing multiple return values to the andReturn() method.

1$client->shouldReceive('post')->andReturn(1, 2);
2 
3$client->post(); // int(1)
4$client->post(); // int(2)

Sometimes, we may need to calculate the return results of method calls based on the arguments passed to the method. This can be accomplished using the andReturnUsing() method, which accepts one or more closures.

1$mock->shouldReceive('post')
2 ->andReturnUsing(
3 fn () => 1,
4 fn () => 2,
5 );

In addition, we can instruct mocked methods to throw exceptions.

1$client->shouldReceive('post')->andThrow(new Exception);

Method Call "Count" Expectations

Along with specifying expected arguments and return values for method calls, we can also set expectations for how many times a particular method should be invoked.

1$mock->shouldReceive('post')->once();
2$mock->shouldReceive('put')->twice();
3$mock->shouldReceive('delete')->times(3);
4// ...

To specify a minimum number of times a method should be called, we may use the atLeast() method.

1$mock->shouldReceive('delete')->atLeast()->times(3);

Mockery's atMost() method allows us to specify the maximum number of times a method can be called.

1$mock->shouldReceive('delete')->atMost()->times(3);

The primary objective of this section is to provide you with an introduction to Mockery, the mocking library we prefer. However, for a more comprehensive understanding of Mockery, we suggest checking out its official documentation. Next, let's explore Pest's plugins and discover how they can enhance your Pest experience: Plugins