Мокирование и заглушки с 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 — локальная установка не требуется.
Все уроки этого курса
- Рабочий процесс разработки через тестирование
- Мокирование и заглушки с Mockery
- Интеграционное и функциональное тестирование
- Мутационное тестирование с Infection