0Pricing
PHP Academy · Урок

Мокирование и заглушки с Mockery

Изолируйте отдельные модули с помощью гибких тестовых замен

«Мокирование и заглушки с Mockery» — бесплатный урок PHP Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

Зачем нужна отдельная библиотека для имитации

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

composer require --dev mockery/mockery

Стаб, мок и шпион

Точная терминология помогает избежать путаницы в тестах:

  • Стаб — возвращает заранее заданные значения; Вы проверяете состояние — результат.
  • Мок — содержит ожидания вызовов; Вы проверяете поведение — был ли он вызван правильно. Несбывшиеся ожидания приводят к ошибке теста.
  • Шпион — записывает вызовы и позволяет проверить их после выполнения.

Практическое правило: для запросов предпочитайте стабы, а для команд — моки и шпионы.

Стаб с разрешением

Используйте Mockery::mock() и allows() (или shouldReceive()->andReturn()), чтобы задать заранее подготовленные возвращаемые значения, не проверяя, был ли вызов выполнен. Здесь стаб шлюза передаёт известный обменный курс, чтобы мы могли отдельно протестировать расчёт.

<?php
use Mockery;
use PHPUnit\Framework\TestCase;

final class ConverterTest extends TestCase
{
    public function test_converts_using_rate(): void
    {
        $rates = Mockery::mock(RateGateway::class);
        $rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);

        $sut = new Converter($rates);
        self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));

        Mockery::close();
    }
}

Мок с ожиданием

Если само взаимодействие является контрактом — например, «электронное письмо должно быть отправлено ровно один раз», — используйте expects() или shouldReceive()->once(). Mockery проверяет ожидание во время вызова Mockery::close(); невыполненный вызов приводит к ошибке теста.

<?php
use Mockery;

$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
       ->send(Mockery::type(Message::class))
       ->once();

$service = new SignupService($mailer);
$service->register('ada@example.com');

Mockery::close(); // fails here if send() was never called

Сопоставление аргументов

Сопоставители Mockery позволяют делать ожидания настолько свободными или строгими, насколько требуется:

  • Mockery::any() — любое значение.
  • Mockery::type('string') / имя класса — проверка типа.
  • Mockery::on(fn($a) => ...) — пользовательский предикат.
  • Mockery::capture($var) — сохранить аргумент для последующих проверок.
<?php
use Mockery;

$repo = Mockery::mock(UserRepo::class);
$repo->expects()
     ->save(Mockery::on(fn(User $u) => $u->isActive()))
     ->once()
     ->andReturnTrue();

Последовательности результатов и динамические возвращаемые значения

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

<?php
use Mockery;

$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
    ->twice()
    ->andThrow(new \RuntimeException('timeout'))
    ->andReturn('{"ok":true}');

// Or derive the return from the input:
$api->shouldReceive('echo')
    ->andReturnUsing(fn(string $in) => strtoupper($in));

Шпионы: проверка после выполнения

Шпион меняет порядок действий: сначала выполните действие, затем проверьте результат. Mockery::spy() записывает вызовы, а затем Вы проверяете их с помощью shouldHaveReceived(). Шпионы сохраняют чистую структуру «подготовка — действие — проверка», когда Вы не хотите загромождать настройку предварительно заданными ожиданиями.

<?php
use Mockery;

$logger = Mockery::spy(Logger::class);

$service = new PaymentService($logger);
$service->charge(500);

// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
       ->with(Mockery::pattern('/charged 500/'))
       ->once();

Mockery::close();

Частичные двойники

Иногда нужен настоящий объект, но с переопределением одного метода, — это частичный двойник. makePartial() из Mockery вызывает настоящие методы, кроме тех, для которых Вы задали ожидания. Используйте такие двойники умеренно: чрезмерная зависимость от них обычно указывает, что класс выполняет слишком много задач.

<?php
use Mockery;

$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);

// real summarize() runs, using the stubbed data
$summary = $report->summarize();

Не создавайте двойники для чужого кода

Основной принцип тестирования: избегайте прямого создания двойников для сторонних классов. Их интерфейсы могут измениться, и Ваш двойник незаметно перестанет соответствовать реальности. Вместо этого скройте их за собственным интерфейсом и создавайте двойник для него. Тогда двойник проверяет Ваш контракт, а интеграционный тест адаптера проверяет настоящую связку.

<?php
interface PaymentGateway {            // you own this
    public function charge(int $cents, string $token): string;
}

final class StripeGateway implements PaymentGateway {
    public function __construct(private \Stripe\StripeClient $client) {}
    public function charge(int $cents, string $token): string {
        return $this->client->paymentIntents->create([/* ... */])->id;
    }
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.

Всегда закрывайте Mockery и проверяйте число вызовов

Два обязательных действия:

  • Вызывайте Mockery::close() в tearDown() (или используйте трейт MockeryPHPUnitIntegration), чтобы ожидания действительно проверялись, а глобальные состояния очищались.
  • Используйте явные количества вызовов (once(), times(n), never()) — расплывчатые моки позволяют ошибкам оставаться незамеченными.
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

final class OrderServiceTest extends TestCase
{
    use MockeryPHPUnitIntegration; // auto-calls Mockery::close()

    public function test_does_not_refund_paid_orders(): void
    {
        $gw = \Mockery::mock(PaymentGateway::class);
        $gw->shouldReceive('refund')->never();
        // ... exercise SUT ...
    }
}

Ожидания порядка вызовов

Иногда порядок вызовов является частью контракта: сначала нужно вызвать beginTransaction(), а затем commit(). Метод ordered() в Mockery требует соблюдения последовательности и приводит к ошибке теста, если вызовы поступают не по порядку. Используйте его только тогда, когда порядок действительно важен: чрезмерная фиксация порядка делает тесты хрупкими.

<?php
use Mockery;

$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();

$service = new TransferService($tx);
$service->run();

Mockery::close(); // fails if commit() happened before begin()

Быстрая проверка

Стаб или мок — что и когда использовать?

Итоги

Вы освоили тестовые двойники Mockery:

  • Стабы (allows) — для запросов; моки (expects) — для команд; шпионы — для проверок после выполнения.
  • Сопоставители аргументов (type, on, capture) позволяют настраивать строгость.
  • Последовательности возвращаемых значений и andReturnUsing моделируют взаимодействующие объекты с состоянием и динамическим поведением.
  • Частичные двойники переопределяют отдельные методы; используйте их умеренно.
  • Не создавайте двойники для того, чем не владеете, — скрывайте сторонние библиотеки за собственным интерфейсом.
  • Всегда вызывайте Mockery::close() и проверяйте явное число вызовов.

Далее: интеграционное и функциональное тестирование.

Часто задаваемые вопросы

Урок «Мокирование и заглушки с Mockery» бесплатный?

Да — полный текст урока «Мокирование и заглушки с Mockery» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Мокирование и заглушки с Mockery»?

Изолируйте отдельные модули с помощью гибких тестовых замен Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Мокирование и заглушки с Mockery»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Рабочий процесс разработки через тестирование
  2. Мокирование и заглушки с Mockery
  3. Интеграционное и функциональное тестирование
  4. Мутационное тестирование с Infection
← Назад к PHP Academy