Створюйте моки для PHP-класів у тестах за допомогою бібліотеки Double

Перекладено ШІ 0 Laravel News 12 серпня, 2026

Творець Laravel Shift презентував Double — нову бібліотеку, що замінює розрізнені mock та spy єдиним універсальним об’єктом. Цей інструмент радикально спрощує тестування у PHP 8.3 та пропонує значно інформативніші повідомлення про помилки.

Double — це нова PHP-бібліотека для створення тест-дублів від Джейсона Маккрірі, автора відомого сервісу Laravel Shift. Double пропонує універсальний підхід замість звичного вибору між mock, spy та partial. Ви просто створюєте дубль класу чи інтерфейсу, а його поведінка визначається методами, які ви викликаєте згодом. Бібліотека потребує PHP 8.3; на момент написання статті актуальна версія — v0.4.0.

Основні можливості пакету:

  • Єдиний конструкторDouble::for() приймає назви класів, інтерфейсів (можна декілька одночасно) або готовий екземпляр об'єкта.
  • Три режими роботи — loose (типовий) повертає безпечні значення для неналаштованих викликів, strict видає помилку, а passthru делегує виклик реальному об'єкту.
  • Два основні методи налаштуванняexpects() для обов’язкових викликів та allows() для опціональних.
  • Функції Spy для кожного дубля — метод received() дозволяє перевірити історію викликів без попереднього оголошення об'єкта як spy.
  • Argument matchers — набір інструментів для перевірки аргументів: Argument::any(), type(), same(), matches(), contains(), capture(), not() та remaining().
  • Інформативні повідомлення про помилки — якщо очікування не справдилося, Double покаже журнал реальних викликів методу.
  • Інтеграція з PHPUnit — невдалі перевірки відображаються як failures, а не errors. Успішні перевірки враховуються як assertions, а спеціальний trait автоматизує верифікацію всіх дублів.
  • Мапінг із Mockery — документація містить детальну інструкцію з перенесення тестів із Mockery на Double.

# Порівняння: один і той самий тест у Mockery та Double

Ось як виглядає ідентичний тест у Mockery:

use Mockery;
 
$repository = Mockery::spy(BookRepository::class);
$repository->shouldReceive('find')->once()->with(123)->andReturn($book);
 
$service = new CatalogService($repository);
$service->lookup(123);
 
$repository->shouldHaveReceived('recordView')->with($book);
 
Mockery::close();

А ось так — у Double:

use JMac\Testing\Double;
 
$repository = Double::for(BookRepository::class);
$repository->expects('find')->with(123)->returns($book);
 
$service = new CatalogService($repository);
$service->lookup(123);
 
$repository->received('recordView')->with($book);

Ключові відмінності:

  • Більше не потрібно обирати між mock() та spy() — метод received() доступний для будь-якого дубля.
  • Метод once() зайвий, оскільки expects() вже передбачає одноразовий виклик.
  • Замість довгих shouldReceive() та andReturn() використовуються лаконічні expects() та returns(). Double уникає зайвих псевдонімів.
  • Mockery::close() не потрібен. Ви можете викликати $repository->verify() вручну або додати trait VerifiesDoubles для автоматичної перевірки.

Повідомлення про помилки також стали зрозумілішими. Якщо замість find('baz') код викличе find('Baz'), Mockery видасть стандартне повідомлення про невідповідність кількості викликів. Double натомість повідомить:

Double `foo` expected `find('baz')` to be called exactly 1 time, but it was never called.
 
The following calls to `find` were made during this test: `find('Baz')`

Double вказує назву класу замість згенерованого ідентифікатора типу Mockery_0_ і чітко показує, які саме аргументи було отримано насправді.

# Створення дубля та вибір режиму

Метод Double::for() повертає об'єкт, який проходить перевірку instanceof для цільового класу чи інтерфейсу:

use JMac\Testing\Double;
 
$repository = Double::for(BookRepository::class);
 
$service = new CatalogService($repository);

Ви можете передати кілька інтерфейсів одночасно — Double створить об'єкт, що реалізує їх усі (аналог intersection types у PHP):

$logger = Double::for(LoggerInterface::class, FlushableInterface::class);

Кожен дубль має один із трьох режимів роботи. **Loose** (типовий) повертає безпечні значення відповідно до типу: false для bool, 0 для int, порожній масив для array тощо. Якщо метод має повертати об'єкт іншого класу, Double автоматично згенерує для нього новий дубль (на один рівень вкладеності).

**Strict** видає помилку при першому ж незапланованому виклику. **Passthru** дозволяє неналаштованим методам виконувати реальну логіку об'єкта, зберігаючи запис про кожен виклик:

$repository = Double::for(BookRepository::class)->strict();
 
$logger = Double::for(Logger::class)->passthru($realLogger);

Оскільки конфігурація відбувається безпосередньо через об'єкт дубля, сім імен методів є зарезервованими: expects, allows, strict, passthru, received, unused та verify. Спроба створити дубль класу, що вже має такі методи, призведе до виключення. Це важливо для Laravel, оскільки метод allows() є частиною контракту Gate.

# Очікування та перевірка аргументів

Синтаксис Double легко читається зліва направо:

$repository->expects('find')->with(123)->returns($book);
$repository->allows('find')->with(999)->throws(new NotFoundException());
$repository->allows('calculateTax')->resolves(fn (...$args) => $gateway->calculateTax(...$args));

expects() вимагає, щоб виклик відбувся (типово — один раз), тоді як allows() робить його необов'язковим.

Кількість викликів у Double налаштовується значно зручніше, ніж у Mockery. Замість купи різних методів (once, twice, atLeast), Double використовує один метод times() з іменованими аргументами:

$repository->expects('save');                      // один раз (типово)
$repository->expects('save')->times(2);            // рівно два рази
$repository->expects('save')->times(1, 3);         // від 1 до 3 разів
$repository->expects('save')->times(minimum: 2);   // щонайменше 2 рази
$repository->allows('save')->times(maximum: 5);    // не більше 5 разів
$repository->allows('save')->never();              // жодного разу

Ці ж самі правила можна застосовувати у методі received() для перевірки постфактум:

$repository->received('save')->with($book)->times(2);

Для гнучкої перевірки аргументів використовується фасад Argument:

use JMac\Testing\Matching\Argument;
 
$repository->allows('save')->with(Argument::type(Book::class))->returns(true);
$repository->allows('find')->with(Argument::matches('/^\d+$/'))->returns($book);
$repository->allows('saveAll')->with(Argument::contains($book))->returns(true);

Метод Argument::capture($var) дозволяє "захопити" аргумент у змінну для подальших перевірок. Також Double підтримує сувору черговість викликів за допомогою методу ordered().

# Верифікація

Метод verify() перевіряє виконання всіх expects(). Якщо ви хочете переконатися, що дубль взагалі не використовувався, скористайтеся unused():

Double `Logger` expected no calls at all, but received: `info('something happened')`.

# Повідомлення про помилки

Double допомагає виправити помилки ще на етапі налаштування. Якщо ви помилилися в назві методу, бібліотека підкаже правильний варіант:

Can't configure `sav` on a double for `BookRepository`. That method
does not exist. Did you mean `save`?

# Інтеграція з PHPUnit

Бібліотека автоматично інтегрується з PHPUnit:

  • Помилки очікувань стають об'єктами AssertionFailedError.
  • Перевірки received() враховуються як assertions (тест не буде позначено як risky).
  • Trait VerifiesDoubles автоматизує виклик verify() після кожного тесту.

# Встановлення

Пакет встановлюється через Composer:

composer require --dev jasonmccreary/double

Щоб працювати з final класами, додайте Double::bypassFinals() у файл завантаження (bootstrap) вашого тестового середовища перед підключенням автозавантажувача.

Докладна документація доступна на testdoublephp.com, а вихідний код — на GitHub.

Популярні

Інше, що варто прочитати

19 Оновлено 26 червня, 2026

Використання штучного інтелекту для управління перекладами в Laravel

Досліджуйте нові можливості локалізації вашого Laravel-додатку з пакунками, які використовують штучний інтелект, такими як ChatGPT та Claude. Які рішення можуть спростити ваш процес перекладу та зробити його більш точним? Читайте далі, щоб дізнатися більше!

51 Оновлено 26 червня, 2026

Nuxt 3 + Laravel Sanctum: Просте та надійне рішення для автентифікації вашого SPA та API

У сучасній веб-розробці аутентифікація є ключовою для захисту додатків і даних користувачів. Дізнайтеся, як модуль nuxt-sanctum-authentication спростить інтеграцію між Nuxt 3 та Laravel Sanctum, забезпечуючи надійний і зручний спосіб реалізації аутентифікації для вашого проєкту

17 Оновлено 26 червня, 2026

Обробка геопросторових даних за допомогою Laravel Magellan

Ви готові відкрити нові горизонти у роботі з геопросторовими даними в Laravel? Дізнайтеся, як за допомогою PostGIS та пакету Laravel-Magellan можна легко зберігати, запитувати та маніпулювати інформацією про розташування, перетворюючи ваші проекти на вражаючі рішення у сфері картографії та геолокації!