Использование phpUnit
Тестирование приватных методов
Идеология тестирования подразумевает, что тестируются только публичные методы. Но иногда есть желание протестировать приватный метод.
Первый вариант — создание и вызов анонимной функции в контексте нужного объекта.
Второй вариант — использование Reflection. Оно позволяет сделать метод публичным и вызвать его.
Вызов приватных методов через замыкание
С помощью Closure и call либо bindTo можно вызвать метод:
class MyServiceTest
{
private MyService $service;
public function setUp(): void
{
$this->service = new MyService();
}
/**
* Вызов метода через замыкание
*
* @return void
*/
public function testCheck(): void
{
$result = (fn($param) => $this->check($param))->call($this->service, $param);
$this->assertTrue($result);
}
/**
* Создание отдельной фунцкии через замыкание
*
* Такой вариант подойдёт когда надо вызвать тестируемый метод несколько раз
*
* @return void
*/
public function testCheck(): void
{
$check = (fn($param) => $this->check($param))->bindTo($this->service, $this->service);
$this->assertTrue($check($first));
$this->assertTrue($check($second));
}
}
Вызов приватных методов через Reflection
Использование Reflection более многословно, но иногда без него никак. Например, если надо протестировать приватный статический метод абстрактного класса, то использование анонимной функции не поможет, так как для абстрактного класса нельзя создать экземпляр, а значит и указать область видимости для анонимной функции не получится.
class MyServiceTest
{
public function testCheck(): void
{
$class = new ReflectionClass(MyService::class);
$method = $class->getMethod('check');
$method->setAccessible(true);
$result = $method->invoke(null, $param);
$this->assertTrue($result);
}
}
Приватный метод и передача по ссылке
Метод, указанный выше, не сработает если метод принимает параметр по ссылке и изменяет его.
class MyService
{
private static function some(array &$data): bool
{
$data['message'] = 'Hello';
return true;
}
private function foo(array &$data)
{
$data['name'] = 'Bar';
return true;
}
}
В таком случае с помощью getClosure можно получить замыкание и вызвать его:
class MyServiceTest
{
/**
* Тестирование приватного статического метода
*
* @return void
* @throws ReflectionException
*/
public function testSome(): void
{
$class = new ReflectionClass(MyService::class);
$method = $class->getMethod('some')->getClosure();
$data = ['name' => 'user name'];
$result = $method($data);
$this->assertTrue($result);
$this->assertArrayHasKey('message', $data);
}
/**
* Тестирование приватного нестатического метода
*
* @return void
* @throws ReflectionException
*/
public function testFoo(): void
{
$class = new ReflectionClass(MyService::class);
// для нестатического метода в getClosure() надо передать объект, который будет установлен в качестве $this
$method = $class->getMethod('foo')->getClosure(new MyService());
$data = ['name' => 'Первоначальное имя'];
$result = $method($data);
$this->assertEquals('Bar', $data['name']);
}
}
Создание моков и обёртки
$this->createStub—protectedметод, вызывающий приватный$this->createMockObject$this->createMock— аналогично предыдущему. Отличаются в возвращаемом результате:
createMockObject() — сахар над getMockBuilder()->getMock():
private function createMockObject(string $originalClassName): MockObject
{
return $this->getMockBuilder($originalClassName)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->getMock();
}
Метод onlyMethods позволяет указать, какие методы из оригинального класса заменить (замокать).
Соответственно реализация остальных методов возьмётся из оригинального класса.
Метод addMethods позволяет добавить методы, которых у класса не существует.
Помогает тестировать магические методы, вызываемые через __call.