При написании тестов часто требуется замокать ответы стороннего API, но при этом часто нужно мокнуть только те методы, которые возвращают ответ, а остальные оставить оригинальными. При этом нужно собрать инстанс сервиса вместе с параметрами конструктора.

Например в Laravel это делается с помощью Service Provider и Mockery

Допустим у нас есть класс, который работает с API платежной системы

<?php

declare(strict_types = 1);

namespace App\\Billing\\Misc\\SomeBank;

use Illuminate\\Http\\Client\\Response;
use Illuminate\\Support\\Facades\\Http;

class SomeBankPayment
{
    protected string $password;

    protected string $username;

    protected string $gatewayUrl;

    protected string $returnUrl;

    public function __construct(string $username, string $password, string $gatewayUrl, string $returnUrl)
    {
        $this->password = $password;
        $this->username = $username;
        $this->gatewayUrl = $gatewayUrl;
        $this->returnUrl = $returnUrl;
    }

    public function registerOrder(SomeBankPaymentOrder $order): Response
    {
        return $this->request('rest/register', \\array_merge($order->toArray(), [
            'returnUrl' => $this->returnUrl . '?' . \\http_build_query(\\array_merge(
                ['orderNumber' => $order->getOrderNumber()],
                $order->getReturnParams()
            )),
        ]));
    }

    public function getOrderStatus($orderId): Response
    {
        return $this->request('rest/getOrderStatus', [
            'orderId' => $orderId,
        ]);
    }

    private function request(string $method, array $data = []): Response
    {
        $url = $this->gatewayUrl . $method . '?' . \\http_build_query(
            \\array_merge([
                'userName' => $this->username,
                'password' => $this->password,
            ], $data)
        );

        return Http::post($url);
    }

}

Сначала зарегистрируем его как провайдер

<?php

declare(strict_types = 1);

namespace App\\Billing\\Providers;

use Illuminate\\Support\\ServiceProvider;
use App\\Billing\\Misc\\SomeBank\\SomeBankPayment;

class SomeBankPaymentProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     */
    public function register(): void
    {
        $this->app->bind(SomeBankPayment::class, static function ($app) {
            $config = $app->make('config');

            return new SomeBankPayment(
                $config->get('billing.drivers.somebank.username'),
                $config->get('billing.drivers.somebank.password'),
                $config->get('billing.drivers.somebank.gateway_url'),
                $config->get('billing.drivers.somebank.return_url')
            );
        });
    }
}
'providers' => \\array_merge(
	...
	\\App\\Billing\\Providers\\SomeBankPaymentProvider::class,
	...
)

Создадим Manager и используем его для создания драйвера

<?php

declare(strict_types = 1);

namespace App\\Billing;

use Illuminate\\Support\\Manager;
use App\\Billing\\Drivers\\SomeBankDriver;
use App\\Billing\\Misc\\SomeBank\\SomeBankPayment;

class BillingManager extends Manager
{
    public function getDefaultDriver()
    {
        return $this->config->get('billing.default_driver');
    }

    protected function createSomebankDriver(): SomeBankDriver
    {
        $payment = \\app()->make(SomeBankPayment::class);

        return new SomeBankDriver(
            $payment
        );
    }
}

Теперь мы можем мокать SomeBankPayment в тестах.

class SomeBankDriverTest extends BillingTestCase
{
	...
	protected function setUp(): void
	{
		$config = $this->app->make('config');
		//Создаем частичный мок с параметрами конструктора
    $mock = Mockery::mock(SomeBankPayment::class, [
        $config->get('billing.drivers.somebank.username'),
        $config->get('billing.drivers.somebank.password'),
        $config->get('billing.drivers.somebank.gateway_url'),
        $config->get('billing.drivers.somebank.return_url'),
    ])->makePartial();
   
		//подменяем его в контейнере
		$this->instance(AlfabankPayment::class, $mock);

		$this->paymentMock = $mock;
	
		$this->manager = $this->app->make(BillingManager::class);
	  $this->driver = $this->manager->driver('somebank');
	}

	public function testSuccess(): void
  {
		...
		//Мокаем ответ от метода registerOrder и getOrderStatus
			$this->paymentMock->shouldReceive('registerOrder')->andReturn(
	        new Response(
	            new \\GuzzleHttp\\Psr7\\Response(200, [], \\json_encode($this->successOrderRegisterResponse))
	        )
	    );

			$this->paymentMock->shouldReceive('getOrderStatus')->andReturn(
            new Response(
                new \\GuzzleHttp\\Psr7\\Response(200, [], \\json_encode($this->successOrderResponse))
            )
        );
		
		//Теперь тут будут мокнутые ответы API
		$result = $this->driver->sessionPayment($paymentRequestedDtoFirst);

		...
	}
	
}

Profit!

P.S. Если вам нравятся мои посты, подписывайтесь на мой канал https://t.me/bearlogin_dev и приглашайте друзей 🙂