[Osaka/Yokohama/Tokushima] Looking for infrastructure/server side engineers!

[Osaka/Yokohama/Tokushima] Looking for infrastructure/server side engineers!

[Deployed by over 500 companies] AWS construction, operation, maintenance, and monitoring services

[Deployed by over 500 companies] AWS construction, operation, maintenance, and monitoring services

[Successor to CentOS] AlmaLinux OS server construction/migration service

[Successor to CentOS] AlmaLinux OS server construction/migration service

[For WordPress only] Cloud server “Web Speed”

[For WordPress only] Cloud server “Web Speed”

[Cheap] Website security automatic diagnosis “Quick Scanner”

[Cheap] Website security automatic diagnosis “Quick Scanner”

[Reservation system development] EDISONE customization development service

[Reservation system development] EDISONE customization development service

[Registration of 100 URLs is 0 yen] Website monitoring service “Appmill”

[Registration of 100 URLs is 0 yen] Website monitoring service “Appmill”

[Compatible with over 200 countries] Global eSIM “Beyond SIM”

[Compatible with over 200 countries] Global eSIM “Beyond SIM”

[If you are traveling, business trip, or stationed in China] Chinese SIM service “Choco SIM”

[If you are traveling, business trip, or stationed in China] Chinese SIM service “Choco SIM”

[Global exclusive service] Beyond's MSP in North America and China

[Global exclusive service] Beyond's MSP in North America and China

[YouTube] Beyond official channel “Biyomaru Channel”

[YouTube] Beyond official channel “Biyomaru Channel”

[Must-see for beginners] From the basics of Mock to how to use it *PHP, Mockery

Thank you everyone for your hard work!

I'm Eita, a budding engineer in my first year of work!

It's been half a year since I joined the workforce, and I'm scared of the speed of time in many ways (the pressure of my second year as a working adult, the education of next year's new graduates, and the impatience of the difference between the passage of time and the speed of technological growth). etc.)

Ma! Putting my anxiety and frustration aside, this time I will talk about mocks when testing with PHP.

I would like to explain "What is a mock?", "Actual usage situations, and various ways to use it"!

Nice to meet you! *This is a method using Mockery.

What is a mock?

First of all, what is a mock? I will explain from there.

Mock-up (model) is an abbreviation for mock-up, which replaces the classes that the test target depends on when performing unit tests verifies whether the classes and methods are called correctly. It's for!

Also, with one of the test doubles, you can specify the return value of the method + verify the call arguments and number of times

However, if this is all you have, you might be wondering what it is all about, so let me give you a specific example of how to use it...

Case 1

We implemented a method for making payments using an external API.

So, when you decide to run the test, if you don't use mocks, you will have to access an external API every time you run the test, incurring charges , and write a description that executes a large number of methods. If you do so, a situation such as `` a large amount of access to the server that provides the external API will cause trouble (unintentional DoS attack) '' may occur.

Case 2 When the prerequisite method is not implemented

Suppose you want to test a method whose behavior changes depending on the bool value that is the return value of the method.

However, we have not yet completed implementing the method that returns a bool value.

What should I do! Well, you could just implement it first, but let's assume for some reason you can't implement it first.

in! Mocks are useful for situations like the above two!

Actual usage (with code)

Basic usage

This time, as an example, let's say we want to test a method that obtains payment information and performs payment using an external payment service (API).

At that time, you need to mock the part that uses the external payment service (API).

I briefly wrote the code for mocking in a series of steps.

The part that uses mocks starts from line 38

    // A class that uses an external payment service class PaymentDataFromExternal { // A method that returns true if the payment is completed successfully public function paymentImmediately(int $paymentId) { //Returns true if the payment is successful return true; } } class PaymentService { public function __construct( private PaymentDataFromExternal $paymentDataFromExternal ){} // Method to be tested public function execPayment() { $payment = fetchPaymet(); // Get payment information // Execute payment from the obtained payment information (external Perform API communication) $isPayment = $this->paymentDataFromExternal->paymentImmediately($payment->id); return $isPayment; } } class Test { public function test_payment_Is the payment executed successfully?() { // Temporary Create payment information for $payment = Payment::factory(); // Class where the method you want to mock exists $this->mock(PaymentDataFromExternal::class, function($mock) { $mock->shouldReceive('paymentImmediately ') // Method name ->with($payment->id) // Argument ->andReturn(true); // Return value }); // Replace the class to be mocked with a mock object $paymentService = app( PaymentService::class); // Method execution replaced by mock object $result = $paymentService->execPayment(); $this->assertTrue($result); } }
    

So, I would like to list some of the mock patterns that I actually encountered (many of which are not directly described in the mockery official documentation).

One thing to note is that in order to use mocks during testing, it seems that DI (dependency injection) must be done properly

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 (return value is Collection) that retrieves all users from the users table.

However, if you just pass the User model as the return value when mocking, you will get angry that the return value is different, and you will not be able to mock it properly.

After looking into it, it seems that mocking requires that the argument and return types match the actual method you want to mock

Therefore, in this case, it was necessary to make the specified class inherit using anonymous class

The actual code is below.

Example: Mock a method that retrieves all user information using a collection type 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 ", you can make it the specified type and successfully mock it.

2. When a mocked method is called multiple times, I want 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 to be tested, and I would like to mock it.

If you want the same return value both times, you can write it like andReturn(true), and it will return true every time the mocked method is called, so there is no problem, but in this case, the first time is true, I want it to return false the second time .

In this case, if you specify something like andReturn(1st time, 2nd time)

Example: If you want to return true the first time and false the second time with a method that determines whether it can be used.

 
    $mock->shouldReceive('isEnabled') ->twice() ->andReturn(true, false);
    

3.I want to set a value to the class property of a mocked object

This time, I want to mock the method that retrieves user information from the users table.

but,

Since a value is set to a class property in a method, I want to verify that the value is set correctly.

Of course, I couldn't set a value to a class property just by mocking it, which was a problem.

In this case, you can set the value to the class property by using andSet(property, value you want to set)

Example: Mock the method that returns user information and set the name property to "Mr. Sato"

       $mock->shouldReceive('getUserInfo') ->once() ->andReturn($user) ->andSet('name', 'Mr. Sato');
    

4. I want to mock some methods of the class to be tested

This time, there is a class that needs to be mocked in the same class as the method to be tested.

If you simply mock the method, other than the method being tested will also be replaced with the mock object.

Therefore, only the method to be tested must be mocked.

In this case, " partial mock " seems to be effective.

However, when I just mocked it using partial mock, it was not replaced with the mock object properly.

When I looked into it...

It seems that a partial mock cannot be replaced with a mock object just by instantiating it, and it seems that it is necessary to explicitly inject dependencies on this side.

    class SampleClass { public function getName() { etc... } public function fetchDataFromExternal() { // Description that performs 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 mentioned above, you can use partial mocks by using makePartial, and you can also create dependencies by writing something like $this->instance(class name::class, partial mock object) Looks like it can be injected.

5. I want to mock static methods

This time, I want to mock the static method that creates the payment history.

However, normal mocking modifies the behavior of an object by mocking instance methods, and static methods use a special method because they are tied to the class itself rather than the instantiated class instance. It seems you need to.

To solve this problem, there is something called alias mock

Example: Mock static method that creates payment history based on payment ID and price

    Mockery::mock('alias:'.History::class)->shouldReeive('makePurchase')->with($payment->id, $payment->price)->andReturn($purchaseHistory);
    

It seems that you can mock a static method by writing it like ('alias:'.class name::class) as shown above

However...

Although mocking static methods is supported using the above method, the official documentation states that it is not recommended

The reason is that an error will occur if a class with the same name exists in two or more tests, so it seems necessary to separate each test.

Solution: You need to write the following in Doc (however, this still causes many other problems, so it is still not recommended).

You can find it by searching the description below.

    /** * @runInSeparateProcess * @preserveGlobalState disabled */
    

Reference source for the above description: kaihttps://docs.phpunit.de/en/11.4/annotations.html

That's all for today's introduction.

The explanations of the methods used above, including once(), with(), andReturn() (there are many others), are described in the official documentation, so please refer to it.

Mockery documentation: https://docs.mockery.io/en/latest/

summary

What do you think? Did it help you a little? ?

I am writing articles about mocking myself, and every time I come across a new pattern that I want to mock, I think to myself, ``How do I do this?!'' and I try to do it every day (maybe...).

So, this time we talked about mocks in test doubles, but test doubles also include stubs, spies, fake objects, and dummy objects.

I'm only aware of the simple behavior myself, so if you're interested, I'd appreciate it if you could look into it and let me know lol.

I'll be posting a lot of information from a budding engineer's perspective in the future, so be sure to add it to your favorites!

If you found this article helpful , please give it a like!
6
Loading...
6 votes, average: 1.00 / 16
168
X facebook Hatena Bookmark pocket
[2025.6.30 Amazon Linux 2 support ended] Amazon Linux server migration solution

[2025.6.30 Amazon Linux 2 support ended] Amazon Linux server migration solution

The person who wrote this article

About the author

Eita

I'm a first-year engineer
and I just want to go out.