lvl2
Отличный выбор!
Сейчас разберём второй уровень по PHPUnit — не просто "проверить, что метод работает", а проверить взаимодействие между объектами через моки.
Это очень важный навык для middle+, потому что:
- Ты проверяешь не только результат, но и процесс.
- Такой подход покрывает именно бизнес-логику.
- Это то, что любят спрашивать на собеседованиях: «А как бы вы проверили, что метод был вызван?»
🧩 Тестирование взаимодействий: моки в PHPUnit
📖 Сценарий
У нас есть такой сервис:
class TaskService { public function __construct( private TaskRepository $taskRepository, private Logger $logger ) {} public function create(TaskDTO $dto): int { $task = new Task( $dto->title, $dto->description, $dto->priority, $dto->assigneeId, new DateTimeImmutable() ); $id = $this->taskRepository->save($task); $this->logger->info('Task created', ['id' => $id]); return $id; } }
🎯 Что мы хотим проверить:
- ✅ Метод
save()вTaskRepositoryвызывается один раз. - ✅ Метод
info()вLoggerвызывается с нужным сообщением и данными. - ✅ Можно проверить, что метод возвращает ID (чисто для полноты).
🛠️ Как пишется тест с моками
use PHPUnit\Framework\TestCase; class TaskServiceTest extends TestCase { public function testCreateTaskShouldSaveTaskAndLogMessage(): void { // Arrange $dto = new TaskDTO( 'Test Task', 'Description', Priority::from('high'), 1 ); // Мокаем TaskRepository $repositoryMock = $this->createMock(TaskRepository::class); $repositoryMock->expects($this->once()) ->method('save') ->with($this->isInstanceOf(Task::class)) ->willReturn(42); // вернём ID // Мокаем Logger $loggerMock = $this->createMock(Logger::class); $loggerMock->expects($this->once()) ->method('info') ->with( 'Task created', ['id' => 42] ); // Act $service = new TaskService($repositoryMock, $loggerMock); $result = $service->create($dto); // Assert $this->assertEquals(42, $result); } }
📦 Объяснения по строчкам
$this->createMock(...);
→ создаём мок объекта.expects($this->once())
→ проверяем, что метод вызван ровно один раз.method('save')
→ указываем, какой метод мока отслеживать.with($this->isInstanceOf(Task::class))
→ проверяем, что в метод передаётся именно объект классаTask.willReturn(42)
→ подменяем поведение мока, чтобы метод вернул 42.- Отдельно
loggerMock: → проверяем, что в лог передаётся именно то сообщение и именно те данные, которые мы ожидаем.
✅ Что мы проверили:
✔️ Поведение: метод save() репозитория вызывается как положено.
✔️ Побочный эффект: метод info() логгера вызывается с правильными параметрами.
✔️ Результат метода: метод возвращает ID 42, который мы подставили в мок.
🎯 Как тебя спросят на собеседовании:
— Как вы тестируете, что внешний сервис (например, Logger или Repository) вызывается?
— Ответ: "Я использую моки в PHPUnit и через методexpects()проверяю количество вызовов, а черезwith()— параметры вызова."
🧩 Мини чек-лист:
| Как |
|---|
expects($this->once())->method('methodName') |
expects($this->never())->method('methodName') |
with($this->equalTo(...)), with($this->isInstanceOf(...)) |
willReturn(value) |
$this->once(), $this->exactly(n) |
Если хочешь — могу дать практику: Я задам тебе схему класса с 2–3 зависимостями, а ты предложишь тест с моками для проверки взаимодействий.
Или идём к следующему уровню тестов — например, snapshot-тесты, data provider, или интеграционные.
Как тебе? 🚀