PHPUnit может быть непосильным для тех, кто только изучает основы программирования и/или модульного тестирования в PHP. Это мощный и надежный инструмент, который уже много лет является краеугольным камнем модульного тестирования в мире PHP, а это означает, что он имеет огромный набор функций, охватывающих практически любой случай, с которым вы можете столкнуться.

Одними из самых мощных возможностей PHPUnit являются заглушки и моки. Они дают вам возможность отделить остальную часть вашего кода от той части, которую вы пытаетесь протестировать. Вы можете имитировать что угодно — от (других) методов в классе, который вы тестируете, до зависимостей, абстрактных классов или даже трейтов и интерфейсов.

Первым шагом, конечно же, является создание макета, и обычно он выглядит следующим образом:

$serviceMock = $this->getMockBuilder(YourService::class)
    ->disableOriginalConstructor()
    ->onlyMethods(['anotherMethodInYourClass', 'yetAnotherMethod'])
    ->getMock();

Помимо методов, показанных в приведенном выше примере, у вас также есть много других, таких как setConstructorArgs() и setMethods(), но мы не будем углубляться в то, что они делают. Что важно в нашем случае, так это то, что существует множество способов того, как именно вы собираетесь создавать mock-объект, в зависимости от ваших потребностей. Вам может понадобиться имитировать набор зависимостей, абстрактный класс, интерфейс,...

Если вы все еще учитесь (или даже если вы не успеваете за изменениями между версиями PHPUnit), вы можете в конечном итоге «угадать», какой набор методов вы собираетесь вызывать при создании своих макетов. Даже StackOverflow полон ответов типа «Мне подходит следующий способ создания макета» — без какой-либо информации или контекста.

Что ж, давайте предоставим некоторый контекст.

PHPUnit внутри создает фиктивный класс точно так же, как вы пишете любой другой класс. Он генерирует исходный код для класса, трейта или интерфейса, который вы пытаетесь имитировать, и заполняет его фиктивным свойством и возвращаемыми значениями. Одна из проверок, которую выполняет PHPUnit, заключается в том, что сгенерированный код фиктивного класса является допустимым кодом PHP. Это делается с помощью менее известной встроенной функции PHP eval($someString). Эта функция оценивает, содержит ли $someString действительный код PHP. Небольшое предупреждение, как говорится в официальной документации: Языковая конструкция eval() очень опасна, поскольку позволяет выполнять произвольный код PHP. Таким образом, его использование не рекомендуется. Если вы тщательно убедились, что нет другого варианта, кроме как использовать эту конструкцию, обратите особое внимание на то, чтобы не передавать в нее какие-либо предоставленные пользователем данные без надлежащей предварительной проверки.

При этом PHPUnit подготавливает исходный код сгенерированного фиктивного класса и сохраняет его в строке, которая затем оценивается с помощью функции eval().

Если вы получаете ошибки при попытке создать или использовать макеты, вы не уверены, что именно происходит под капотом и как выглядит созданный макетный класс на самом деле — есть способ увидеть фактическое содержимое (полный исходный код) фиктивного класса во время отладки.

Ключевым моментом, на который следует обратить внимание, является класс PHPUnit\Framework\MockObject\MockClass и его метод generate(). В PHPUnit 9.6 это выглядит так:

public function generate(): string
    {
        if (!class_exists($this->mockName, false)) {
            eval($this->classCode);

            call_user_func(
                [
                    $this->mockName,
                    '__phpunit_initConfigurableMethods',
                ],
                ...$this->configurableMethods
            );
        }
        return $this->mockName;
    }

Если вы установите точку останова на eval($this->classCode), вы сможете увидеть содержимое $this->classCode, которое, очевидно, содержит полный исходный код сгенерированного фиктивного класса. Самое приятное то, что он даже правильно отформатирован, так что у вас не возникнет проблем с его чтением.

Теперь вы можете свободно экспериментировать с различными типами макетов и вариантами их создания, наблюдая при этом, как именно ваш способ создания макетов влияет на конечный результат — объект макета. Веселиться!