Потрясающая библиотека Mockery, которая сделает вашу жизнь лучше во время написания тестов. Она помогает создавать быстрее и проще стабы и моки и так же используется в большинстве тестовых фрейморков. И что самое приятное, библиотека очень простая.
Его основная цель состоит в том, чтобы предложить тестовую двойную инфраструктуру с лаконичным API, способным четко определять все возможные объектные операции и взаимодействия с использованием удобочитаемого предметно-ориентированного языка (DSL).
документация Mockery
Установка Mockery
composer require --dev mockery/mockery
Создание тестовых двойников Mockery
Огрызки(Stubs) и Заглушки(Mocks) в Mockery
Создать стаб/мок можно для класса, интерфейса или класса с интерфейсами:
$mock = Mockery::mock( 'SomeClassName' );
$mock = Mockery::mock( 'SomeInterfaceName' );
$mock = Mockery::mock( 'SomeClassName, SomeInterfaceName, SomeInterfaceOtherName' );
Затем можно описать поведение мока можно с помощью метод. Например:
$mock = Mockery::mock( 'SomeClassName' );
$mock
->shouldReceive( 'some_method' )
->with( 'arg1', 'arg2' )
->times( 4 )
->andReturn( 'awesome' );
В целом детально описываем, что в данном тесте должен быть вызван метод some_method
с аргументами 'arg1'
, 'arg2'
, 4 раза и вернуть строку awesome
.
Шпионы(Spies) в Mockery
Создать шпиона можно для класса, интерфейса или класса с интерфейсами:
$mock = Mockery::spy( 'SomeClassName' );
$mock = Mockery::spy( 'SomeInterfaceName' );
$mock = Mockery::spy( 'SomeClassName, SomeInterfaceName, SomeInterfaceOtherName' );
Пример:
$spy = Mockery::spy( 'SomeClassName' );
$spy->foo();
$spy->shouldHaveReceived()->foo();
Шпионы отличаются от моков тем, что их задача заключается в том, чтобы проверить, что метод был вызван после вызова тестируемого метода. Для шпионов можно вызвать абсолютно любой метод во время теста, а потом проверить, что метод был вызван.
Тестовые двойники для абстрактных классов и интерфейсов
Тестовые двойники для абстрактных классов и интерфейсов создаются точно так же, как и для обычных классов:
$mock = Mockery::mock( 'SomeClassName' );
$mock = Mockery::mock( 'SomeInterfaceName' );
$spy = Mockery::spy( 'SomeClassName' );
$spy = Mockery::spy( 'SomeInterfaceName' );
Частичные тестовые двойники
Частичный тестовый двойник это реальный класс с реальными методами, который обладает всеми свойствами обычного двойника. Так же частичные двойники вызываются БЕЗ вызова конструктора по умолчанию.
Создаем простой мок или шпиона и делаем из него частичного тестового двойника с помощью вызова метода makePartial
, затем методы этого класса можно будет вызывать или переопределить с помощью shouldReceive
:
class Some_Class {
public function some_method() { return 123; }
}
$some_class = mock( Some_Class::class )->makePartial();
$some_class->some_method(); // int(123);
$some_class->shouldReceive( 'some_method' )->andReturn( 456 );
$some_class->some_method(); // int(456)
Так же можно сделать частичный двойник у которого можно указать явно, какие методы должны быть у него реальные:
$some_class = mock( 'Some_Class[some_method]' )->makePartial();
$some_class->some_method(); // int(123);
$some_class = mock( 'Some_Class[!some_method]' )->makePartial();
$some_class->some_method(); // Error
Думаю тут понятно, что при создании мока можно указать, какие методы реальные или какие «фейковые» с помощью символа !(отрицания).
Если нам нужно вызвать частичный тестовый двойник с конструктором или передать в конструктор аргументы то их можно передать через массив во 2-й или 3-й параметр:
$mock = Mockery::mock( 'MyClass', [] )->makePartial();
$mock = Mockery::mock( 'MyClass', [ $arg1, $arg2 ] )->makePartial();
$mock = Mockery::mock( 'MyClass', 'MyInterface', [ $arg1, $arg2 ] )->makePartial();
Тестовые двойники для final классов
Так как final классы нельзя наследовать, то необходимо использовать данную конструкцию для создание мока для них:
final class MyClass {
// ...
}
$mock = Mockery::mock( new MyClass );
$mock = Mockery::spy( new MyClass );
Тестовый двойник с свойствами и методами другого класса
С помощью метода namedMock()
мы можем расширить нашего тестового двойника. Пример тестируемого класса:
class Fetcher {
const SUCCESS = 0;
const FAILURE = 1;
public function fetch() {
return self::SUCCESS;
}
}
class MyClass {
public function doFetching( $fetcher ) {
$response = $fetcher->fetch();
if ($response == Fetcher::SUCCESS) {
echo "Thanks!" . PHP_EOL;
} else {
echo "Try again!" . PHP_EOL;
}
}
}
Мы хотим проверить успешный сценарий и для этого нам нужно будет добавить статические свойства
class FetcherStub {
const SUCCESS = 0;
const FAILURE = 1;
}
$mock = Mockery::mock('Fetcher', 'FetcherStub')
$mock->shouldReceive('fetch')
->andReturn(0);
$myClass = new MyClass();
$myClass->doFetching($mock);
Теперь тестовый двойник обладает статическими методами FetcherStub
и мы можем протестировать наш класс.
Псевдоними(alias) для Mockery
Псевдонимы нужны для того, чтобы сделать заглушки для статических методов:
class MyClass {
public static function some_method() {
return 'ne ok';
}
}
$mock = Mockery::mock( 'alias:MyClass' );
$mock
->shouldReceive( 'some_method' )
->andReturn( 'ok' );
$this->assertSame( 'ok', MyClass::some_method() );
Перезагрузка(Overload) классов с помощью Mockery
С помощью overload вы можете создать глобальный мок, который будет доступен при создании новых объектов. С данной фичей очень аккуратно нужно работать и в очень крайних случаях. Если вы используете ее, то скорее всего у кого-то говнокод :). Но это оправдано при тестировании порождающих паттернов например какой-нибудь фабрики.
$mock = Mockery::mock( 'overload:MyClass' );
new MyClass(); // Mock
new MyClass(); // The same mock.
С помощью overload можно переопределить Hard Dependency. Как это сделать можно посмотреть в статье: Как тестировать Hard Dependencies.
Заглушка для типов данных
Мы можем указать, что метод будет вызван с определенным параметром. Вариантов, как это сделать просто очень много:
->with( 1 ); // === затем ==
->with( Mockery::any() ); // Любое значение
->with( Mockery::anyOf( 1, 2 ); // Любое из переданных значение
->with( Mockery::not( 2 ) ); // Любое значение, кроме 2
->with( Mockery::notAnyOf( 1, 2 ) ); // Любое кроме переданных значений
->with( Mockery::type('array') ); // Тип array (можно указать любой из примитивных типов)
->with(
Mockery::on( function ( $arg ) { // Тип должен удовлетворять проверку в анонимной функции. В этом случае 2, 4, 6 - ок; 1, 3, 5 - не ок
return 0 === $arg % 2;
} )
);
->with( Mockery::pattern( '/^foo/' ) ); // Аргумент должен соответствовать регулярному выражению
->with( Mockery::ducktype( 'foo', 'bar' ) ); // Тип класса должен быть одним из аргументов
->with( Mockery::capture( $bar ) ); // Должна быть использована локальная переменная $bar
->with( Mockery::subset( [ 0 => 'foo' ] ) ); // Массив содержит выражение
->with( Mockery::contains( 'value1', 'value2' ) ); // Массив содержит ключи
->with( Mockery::hasKey( 'key' ) ); // Массив содержит ключ
->with( Mockery::hasValue( 'value' ) ); // Массив содержит значение
Заглушки для защищенных методов
В документации не рекомендуют это делать, но все же Mockery это умеет делать. Я уже описывал как тестировать приватные методы и все сложности связанные с этим.
class MyClass{
protected function foo() {
}
}
$mock = Mockery::mock('MyClass')
->shouldAllowMockingProtectedMethods();
$mock->shouldReceive('foo');
Вывод
Mockery помогает легче использовать тестовых двойников(стабы/моки/шпионы) и расширяет их функциональность. Так же очень сильно упрощает тестирование с помощью частичных моков, дает возможность создавать заглушки для типов данных и переопределять глобально классы для тестирования жестких зависимостей. Для написания тестов под WordPress вам точно понадобиться данная библиотека.