[Must-see for beginners] Mock basics and usage guide ※PHP, Mockery

table of contents
- 1 What is a mock?
- 2 Actual usage (with code)
- 2.1 1. I want to return the return value of a mocked method as an object that inherits some class
- 2.2 2. When a mocked method is called multiple times, I want it to return a different value depending on the number of times it is called
- 2.3 3. I want to set a value for a class property of a mocked object
- 2.4 4. I want to mock some methods of the class under test
- 2.5 5. I want to mock static methods
- 3 summary
Thank you everyone for your hard work!
I'm Eita, a budding engineer in my first year of working life!
It's been six months since I started working, and I'm feeling scared in many ways by how quickly time passes (the pressure of my second year, the training of new graduates next year, the frustration of the difference in the speed of time and technological growth, etc.)
Anyway, putting aside my anxieties and impatience, this time I'll be talking about using `mock` when running tests in PHP.
I would like to explain what a mock is, the actual situations in which it is used, and various ways to use it!
Thank you! *This is a method using Mockery
What is a mock?
First, let me explain what a mock is
SMART is an abbreviation for mock-up, which is used when unit testing the behavior of implemented code. It involves replacing the classes that the code under test depends on with mock-ups to verify that those classes and methods are being called correctly !
Additionally, one of the test doubles allows you to specify the method's return value and verify the call arguments and number of calls
But if that's all there is to it, it might seem a bit pointless, so here's a concrete example of how to use it..
Case 1
We have implemented a method to make payments using an external API
Therefore, when you decide to run a test, if you haven't used mocks, you'llincur charges. Also, if the code is written in a way that executes the relevant method a lot,you might cause problems (an unintentional DoS attack) by making a large number of accesses to the server providing the external API.

Case 2: When the required method is not implemented
Suppose you want to test a method whose behavior changes depending on the Boolean value returned by the method
However, we have not yet completed the implementation of the method that returns a boolean value
What should I do? Well, I could just implement it first, but let's assume that for some reason I can't implement it first
So! Mocks are useful in the above two situations!
Actual usage (with code)
Basic usage
As an example, we will now test a method that obtains payment information and uses an external payment service (API) to make the payment
In this case, you will need to mock the parts that use external payment services (APIs)
I have written a rough outline of the code required for mocking
The part using mocks starts from line 38
// Class that uses an external payment service class PaymentDataFromExternal { // Method that returns true if payment is completed successfully public function paymentImmediately(int $paymentId) { // Executes payment and returns true if successful return true; } } class PaymentService { public function __construct( private PaymentDataFromExternal $paymentDataFromExternal ){} // Method to be tested public function execPayment() { $payment = fetchPaymet(); // Obtains payment information // Executes payment from the obtained payment information (communicates with external API) $isPayment = $this->paymentDataFromExternal->paymentImmediately($payment->id); return $isPayment; } } class Test { public function test_payment_was payment executed successfully?() { // Create temporary payment information $payment = Payment::factory(); // Class containing the method to be mocked $this->mock(PaymentDataFromExternal::class, function($mock) { $mock->shouldReceive('paymentImmediately') // Method name ->with($payment->id) // Arguments ->andReturn(true); // Return value }); // Replace the mocked class with a mock object $paymentService = app(PaymentService::class); // Execute the method replaced with the mock object $result = $paymentService->execPayment(); $this->assertTrue($result); } }
So, I would like to list some of the mocking patterns I have actually encountered (most of which are not directly described in the official mockery documentation)
One thing to note is that in order to use mocks during testing, it seems that proper dependency injection (DI) must be in place
1. I want to return the return value of a mocked method as an object that inherits some class
I want to mock a method (returning a Collection) that retrieves all users from the users table
However, if you simply pass the User model as the return value when mocking, you will get an error saying that the return value is different and you will not be able to mock it properly
Upon investigation, it appears that mocks need to have argument and return types that match the actual method being mocked
Therefore, in this case, it was necessary to use PHP'sanonymous classto inherit from the specified class.
The actual code is as follows:
Example: Mock a method to get all user information as a collection based on user_id
$mock->shouldReceive('fetchUsersById') ->once() ->with($user->id) ->andReturn(new class extends Illuminate\Database\Eloquent\Collection([$userMock]));
As shown above, by writing "new class extends the class you want to inherit from", you can specify the type and successfully mock it.
2. When a mocked method is called multiple times, I want it to return a different value depending on the number of times it is called
This time, there is a method that is called twice among the methods being tested, and I would like to mock this method
If the same return value is acceptable both times, you can write it as andReturn(true), and there will be no problem as the mocked method will return true every time it is called. However,in this case, we want it to return true the first time and false the second time.
In this case, specifying it as " andReturn(1st time, 2nd time) " will return different values depending on how many times it has been called
Example: If you want to return true the first time and false the second time in a method that determines whether something is available
$mock->shouldReceive('isEnabled') ->twice() ->andReturn(true, false);
3. I want to set a value for a class property of a mocked object
This time, we want to mock the method that retrieves user information from the users table
but,
I want to verify that the value is set correctly because I am setting a value to a class property within a method
I was in trouble because I couldn't just mock it and set values to the class properties
In such cases, you can set a value to a class property by using " andSet(property, value to set) "
Example: Mock a method that returns user information and set the name property to "Sato-san"
$mock->shouldReceive('getUserInfo') ->once() ->andReturn($user) ->andSet('name', 'Mr. Sato');
4. I want to mock some methods of the class under test
In this case, there is a class that needs to be mocked in the same class as the method being tested
If you simply mock it, methods other than the one being tested will also be replaced with mock objects
Therefore, you should only mock the methods you are testing
In this case, a "partial mock-up" seems to be effective.
However, if you simply mock using a partial mock, it will not be properly replaced with a mock object
Upon investigation, I found that..
It seems that partial mocks cannot be replaced with mock objects simply by instantiating them; it seems that we need to explicitly inject dependencies on this side
class SampleClass { public function getName() { etc... } public function fetchDataFromExternal() { // Code to perform external communication } } class Test { $partialMock = Mockery::mock(SampleClass::class)->makePartial(); $mock->shouldReceive('fetchDataFromExternal') ->once(); // By writing the following, when the class is called, it will be replaced with $partialMock $this->instance(SampleClass::class, $partialMock); }
As described above, you can use partial mocks by using makePartial, and it seems that you can also perform dependency injection by writing it like this : $this->instance(ClassName::class, Partial Mock Object)
5. I want to mock static methods
This time, I want to mock the static method that creates payment history
However, normal mocking involves mocking instance methods to change the behavior of an object, and static methods are tied to the class itself, not to the instance of the instantiated class, so it seems necessary to use a special method
And to solve this problem, something called "alias smock" is available.
Example: Mock a static method that creates a payment history based on the payment ID and price
Mockery::mock('alias:'.History::class)->shouldReeive('makePurchase')->with($payment->id, $payment->price)->andReturn($purchaseHistory);
As shown above , it seems that static methods can be mocked by writing them as " ('alias:'.classname::class) "
However..
While mocking static methods is technically supported using the method described above, the official documentation states that it is not recommended
The reason is that if two or more tests have a class with the same name, an error occurs, so it seems necessary to separate each test
Solution: You need to add the following to the doc (but this still has many other issues so is not recommended):
You can find it by checking the description below
/** * @runInSeparateProcess * @preserveGlobalState disabled */
Reference for the above information:kaihttps://docs.phpunit.de/en/11.4/annotations.html
That's all for this introduction
The official documentation provides explanations of the methods used above, including once(), with(), and Return() (there are many others), so please refer to it
Mockery documentation:https://docs.mockery.io/en/latest/
summary
What do you think? Was it of some help to you?
I myself write articles about mockups, and every time I come across a new pattern that I want to mock, I think to myself, "How am I going to do that?!", but I manage to do my best every day (probably...)
So, this time we looked at mocks, which are a type of test double, but there are other types of test doubles: stubs, spies, fake objects, and dummy objects
I'm only aware of simple behaviors, so if you're interested, I'd be grateful if you could research it and let me know
I'll be posting lots of information from the perspective of a budding engineer in the future, so be sure to add me to your favorites!
8
