Чудо Mockery для заглушек в unit тестах

Чудо Mockery для заглушек в unit тестах

Потрясающая библиотека Mockery , которая сделает жизнь лучше во время написания тестов.

Она помогает создавать быстрее и проще стабы и моки и так же используется в большинстве тестовых фрейморков. И что самое приятное, библиотека простая.

Его основная цель состоит в том, чтобы предложить тестовую двойную инфраструктуру с лаконичным API, способным четко определять все возможные объектные операции и взаимодействия с использованием удобочитаемого предметно-ориентированного языка(DSL).

документация Mockery

Установка Mockery

composer require --dev mockery/mockery

Создание тестовых двойников Mockery

Огрызки(Stubs) и Заглушки(Mocks) в Mockery

Создать стаб/мок можно легко для класса, интерфейса или класса с интерфейсами:

$mock = \Mockery::mock( '\Some\Class\Name');$mock = \Mockery::mock( '\Some\Interface\Name');$mock = \Mockery::mock( '\Some\Class\Name, \Some\Interface\Name, \Some\Interface\OtherName');

Далее можно легко описать поведение мока можно при помощи способ. Например:

$mock = \Mockery::mock( '\Some\Class\Name');$mock->shouldReceive( 'some_method')->with( 'arg1', 'arg2')->times( 4)->andReturn( 'awesome');

В целом детально описываем, что в данном тесте должен быть вызван способ some_method с аргументами 'arg1', 'arg2', 4 раза и вернуть строку awesome.

Шпионы(Spies) в Mockery

Создать шпиона можно для класса, интерфейса или класса с интерфейсами:

$mock = \Mockery::spy( '\Some\Class\Name');$mock = \Mockery::spy( '\Some\Interface\Name');$mock = \Mockery::spy( '\Some\Class\Name, \Some\Interface\Name, \Some\Interface\OtherName');

Пример:

$spy = \Mockery::spy( '\Some\Class\Name');$spy->foo();$spy->shouldHaveReceived()->foo();

Шпионы отличаются от моков тем, что их задача заключается в том, чтобы проверить, что способ был вызван после вызова тестируемого способа. Для шпионов можно без труда вызвать абсолютно любой способ во время теста, а потом проверить, что способ был вызван.

Тестовые двойники для абстрактных классов и интерфейсов

Тестовые двойники для абстрактных классов и интерфейсов создаются точно так же, как и для обычных классов:

$mock = \Mockery::mock( '\Some\Class\Name');$mock = \Mockery::mock( '\Some\Interface\Name');$spy = \Mockery::spy( '\Some\Class\Name');$spy = \Mockery::spy( '\Some\Interface\Name');

Частичные тестовые двойники

Частичный тестовый двойник это реальный класс с реальными способами, который обладает всеми свойствами обычного двойника. Так же частичные двойники вызываются БЕЗ вызова конструктора по умолчанию.

Создаем простой мок или шпиона и делаем из него частичного тестового двойника при помощи вызова способа 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(); // Mocknew 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 вам точно понадобиться данная библиотека.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *