Что такое мокирование в тестировании?

Ответ

Мокирование — это техника в модульном тестировании, при которой реальные зависимости объекта заменяются контролируемыми объектами-заглушками (моками). Это позволяет изолировать тестируемый код, задавать ожидаемое поведение зависимостей и проверять взаимодействие с ними.

Зачем это нужно: Например, чтобы протестировать сервис оплаты, не совершая реальных транзакций через платежный шлюз. Мы создаем мок шлюза, который всегда возвращает успешный ответ.

Пример с PHPUnit и моком:

// Интерфейс внешней зависимости
interface PaymentGatewayInterface {
    public function charge(float $amount): bool;
}

// Класс, который мы тестируем
class PaymentService {
    private PaymentGatewayInterface $gateway;

    public function __construct(PaymentGatewayInterface $gateway) {
        $this->gateway = $gateway;
    }

    public function processOrder(float $amount): bool {
        // Бизнес-логика...
        return $this->gateway->charge($amount);
    }
}

// Тест с использованием мока
class PaymentServiceTest extends PHPUnitFrameworkTestCase {
    public function testProcessOrderCallsGatewayWithCorrectAmount(): void {
        // 1. Создаем мок объекта
        $gatewayMock = $this->createMock(PaymentGatewayInterface::class);

        // 2. Настраиваем ожидания: метод charge будет вызван ровно один раз с аргументом 100.0 и вернет true
        $gatewayMock->expects($this->once())
                    ->method('charge')
                    ->with($this->equalTo(100.0))
                    ->willReturn(true);

        // 3. Внедряем мок и тестируем
        $service = new PaymentService($gatewayMock);
        $result = $service->processOrder(100.0);

        // 4. Проверяем результат
        $this->assertTrue($result);
    }
}

В своей работе я часто использую моки для тестирования сервисов, которые работают с базой данных, внешними API, почтовыми рассылками или файловой системой. Это делает тесты быстрыми и независимыми от внешнего окружения.

Ответ 18+ 🔞

А, мокирование, ёпта! Ну это же просто святое дело для любого, кто хоть раз пытался написать тест, который не ломается из-за того, что у соседнего сервера траванулся жёсткий диск. Сейчас объясню на пальцах, без этой заумной книжной хуйни.

Представь, тебе надо проверить, правильно ли твой код звонит в банк, чтобы списать бабки. Но ты же не будешь при каждом запуске теста реально тысячу рублей с карты снимать? Это же пиздец, через неделю тестирования ты станешь бомжом. Вот для этого и нужны моки — такие подставные уроды, которые притворяются настоящими сервисами.

Суть в чём: Берёшь свою хитрую жопу, которая зависит от какой-то внешней херни (база данных, платёжка, отправка смс), и подсовываешь ей вместо настоящей зависимости её кривую картонную копию, которой ты полностью управляешь. И говоришь ей: «Слушай, вот этот твой PaymentGateway — он теперь будет всегда говорить «ок», когда я попрошу, и никуда на самом деле звонить не будет». И ты спокоен.

Вот смотри, как это выглядит в коде, на примере этой платежной хрени:

// Это интерфейс, контракт на то, как должна выглядеть платёжная заглушка
interface PaymentGatewayInterface {
    public function charge(float $amount): bool;
}

// А это наш главный герой — сервис, который надо протестировать
class PaymentService {
    private PaymentGatewayInterface $gateway;

    public function __construct(PaymentGatewayInterface $gateway) {
        $this->gateway = $gateway;
    }

    public function processOrder(float $amount): bool {
        // Тут какая-то своя логика, почесали репу, посчитали скидку...
        // А потом БАЦ — идём в шлюз деньги списывать
        return $this->gateway->charge($amount);
    }
}

// А ТЕПЕРЬ САМОЕ ВКУСНОЕ — ТЕСТ!
class PaymentServiceTest extends PHPUnitFrameworkTestCase {
    public function testProcessOrderCallsGatewayWithCorrectAmount(): void {
        // 1. Творим муляж! Берём и создаём мок.
        // Теперь $gatewayMock — это не настоящий шлюз, а его злобный клон-оборотень.
        $gatewayMock = $this->createMock(PaymentGatewayInterface::class);

        // 2. Настраиваем этого ублюдка. Говорим ему строго:
        // «Метод `charge` должен быть вызван РОВНО ОДИН РАЗ (`$this->once()`),
        // и вызван с аргументом 100.0 (`->with($this->equalTo(100.0))`),
        // и когда вызовут — ты должен вернуть `true` (`->willReturn(true)`)».
        // Если что-то пойдёт не так — PHPUnit взбесится и тест провалится.
        $gatewayMock->expects($this->once())
                    ->method('charge')
                    ->with($this->equalTo(100.0))
                    ->willReturn(true);

        // 3. Подсовываем нашему сервису эту липу вместо настоящего шлюза.
        $service = new PaymentService($gatewayMock);
        // Запускаем логику.
        $result = $service->processOrder(100.0);

        // 4. Смотрим, что получилось. Если всё ок — тест зелёный.
        $this->assertTrue($result);
        // И ты сидишь довольный, потому что проверил логику, не потратив ни копейки.
    }
}

Вот и вся магия, ебать мои старые костыли. Я эту хуйню постоянно юзаю: когда тестирую что-то, что лезет в базу (чтобы не засирать её тестовыми данными), когда работаю с внешним АПИ (которое может тупить или вообще лежать), или с отправкой писем (чтобы весь ящик не заспамить). Тесты летают как угорелые, потому что от внешнего мира не зависят вообще. Доверия ебать ноль к этим внешним сервисам, а с моками — полный контроль. Красота же!