Кто еще не знаком с тестированием и модульным тестированием можете ознакомится: Автоматизация тестирования, Модульное тестирование с помощью PHPUnit.
Тестирование тем и плагинов под WordPress имеет одну большую проблему — взаимодействие с ядром. Решить ее можно с помощью библиотек Brain-WP/BrainMonkey или 10up/WP_Mock. Как писать тесты с помощью 10up/WP_Mock вы можете прочитать в статье: Модульное тестирование WordPress (PHPUnit, WP_Mock), но а сейчас разберемся с Brain Monkey.
Библиотека Brain Monkey помогает делать заглушки для функций и классов из ядра WordPress.
Установка библиотеки Brain-WP/BrainMonkey для тестирования WordPress
Устанавливаем библиотеку через composer:
composer require --dev brain/monkey
Для работы библиотеки нужно использовать фикстуры setUp
и tearDown
:
use BrainMonkey;
use PHPUnitFrameworkTestCase;
class Test_Main extends TestCase {
public function setUp(): void {
parent::setUp();
MonkeysetUp();
}
public function tearDown(): void {
MonkeytearDown();
parent::tearDown();
}
}
Теперь мы можем делать заглушки абсолютно для любых функций и классов WordPress.
Возможности библиотеки
BrainMonkeyFunctionswhen;
Данная функция нужна для того, чтобы при вызове функции вернуть какой-то результат.
justReturn
use function BrainMonkeyFunctionswhen;
when( 'function_name' )->justReturn( 'krya' );
$this->assertSame( 'krya', function_name() );
При помощи when
мы делаем мок для функции function_name
и при ее вызове будет возвращен результат из метода justReturn
.
returnArg
when( 'func1' )->returnArg();
when( 'func2' )->returnArg( 2 );
when( 'func3' )->returnArg( 3 );
$this->assertSame( 'krya', func1( 'krya', 2, 3 ) );
$this->assertSame( 'krya', func2( 1, 'krya', 3 ) );
$this->assertSame( 'krya', funct3( 1, 2, 'krya' ) );
При помощи when
мы делаем мок для функции function_name
и при ее вызове будет возвращен аргумент функции под номером из метода returnArg
.
Это очень удобно, когда нам нужно использовать функции для очистки переменных или очистки данных перед выводом(sanitize_*, esc_*).
justEcho
when( 'function_name' )->justEcho( 'krya' );
ob_start();
function_name();
$this->assertSame( 'krya', ob_get_clean() );
При вызове функции function_name
выводим на экран текст из метода justEcho
.
alias
when( 'duplicate' )->alias( function ( $value ) {
return "Was " . $value . ", now is " . ( $value * 2 );
} );
$this->assertSame( 'Was 1, now is 2', duplicate( 1 ) );
Вызов функции duplicate
мы заменяем на нашу функцию.
when( 'bigger' )->alias( 'strtoupper' );
$this->assertSame( 'WAS LOWER', bigger( 'was lower' ) );
При вызове функции bigger
будет вызвана функция strtoupper
.
BrainMonkeyFunctionsstubs;
Функция для массовой замены функций:
stubs(
[
'esc_attr',
'esc_html',
'esc_textarea',
'__',
'_x',
'esc_html__',
'esc_html_x',
'esc_attr_x',
]
);
$this->assertSame( 'krya', esc_attr( 'krya' ) );
$this->assertSame( 'krya', esc_html( 'krya' ) );
При вызове каждой из функций будет возвращаться первый аргумент, который был в нее передан. Тоже самое что и when( 'func1' )->returnArg()
, но массово.
Так же можно вторым параметром указать результат функций:
stubs(
[
'is_user_logged_in',
'current_user_can',
],
true
);
Или еще круче, передав массив ключ-значени, в котором ключ — название функции, а значении примитив или callback.
stubs(
[
'is_user_logged_in' => true,
'current_user_can' => false,
'bigger' => 'strtoupper',
]
);
$this->assertTrue( is_user_logged_in() );
$this->assertFalse( current_user_can() );
$this->assertSame( 'WAS LOWER', bigger( 'was lower' ) );
Подводные камни
null
нельзя передать, как результат функции, поэтому используйте __return_null
для этого:
stubs(
[
'return_null_function' => '__return_null',
]
);
$this->assertNull( return_null_function() );
Если вам нужно замокать функцию, которая возвращает callback
. Поэтому нужно указать callback
, который возвращает callback
.
stubs(
[
'return_callback' => function () {
return [ 'Callback_Class', 'callback_method' ];
},
]
);
$this->assertSame(
[ 'Callback_Class', 'callback_method' ],
return_callback()
);
BrainMonkeyFunctionsexpect
Основной инструмент для заглушек функций. В нем можно указать сколько раз, с какими аргументами и что вернула функция.
Функция была вызвана n-раз
expect( 'once' )->once(); // 1
expect( 'twice' )->twice(); // 2
expect( 'i_dont_know' )->zeroOrMoreTimes();
expect( 'no_more_than_one' )->atLeast()->once(); // <=1
expect( 'more_than_one' )->atMost()->once(); // >= 1
expect( 'never' )->never(); // 0
expect( 'three_times' )->times( 3 ); // 3
expect( 'from_2_to_4_times' )->between( 2, 4 ); // 2-4
Функция была вызвана с такими параметрами:
expect( 'function_name' )->once()->withAnyArgs();
function_name( 1, 2, 3 );
expect( 'function_name' )->once()->withNoArgs();
function_name();
expect( 'function_name' )->once()->with( 'arg1', 'arg2' );
function_name( 'arg1', 'arg2' );
expect( 'function_name' )
->once()
->with( Mockery::type( 'int' ), Mockery::type( 'string' ) );
function_name( 10, 'string' );
expect( 'function_name' )->once()->with( Mockery::any() );
function_name( new stdClass() );
expect( 'function_name' )->once()->with( Mockery::anyOf( 'a', 2, true ) );
function_name( 'a' );
expect( 'function_name' )->once()->with( Mockery::not( 'a', 2, true ) );
function_name( 1 );
withAnyArgs()
— с любыми аргументами, с любым их количеством или вовсе без них;withNoArgs()
— без аргументов;with( ... )
— указываем точные аргументы.with( Mockery::type( 'int' ), Mockery::type( 'string' ) )
— первый аргумент любое число, а второй — любая строка;with( Mockery::any() )
— первый аргумент может быть абсолютно любого типа;with( Mockery::anyOf( 'a', 2, true ) )
— первый аргумент любой из списка;with( Mockery::not( 'a', 2, true ) )
— любой, кроме тех, что в списке.
Функция возвращает
expect( 'function_name' )->once()->andReturn( 'Baz!' );
$this->assertSame( 'Baz!', function_name() );
expect( 'function_name' )
->twice()
->andReturn( 'First time I run', 'Second time I run' );
$this->assertSame( 'First time I run', function_name( 'First time I run' ) );
$this->assertSame( 'Second time I run', function_name( 'Second time I run' ) );
expect( 'function_name' )
->twice()
->andReturnValues( [ 'First time I run', 'Second time I run' ] );
$this->assertSame( 'First time I run', function_name( 'First time I run' ) );
$this->assertSame( 'Second time I run', function_name( 'Second time I run' ) );
expect( 'function_name' )
->once()
->andReturnNull();
$this->assertNull( function_name() );
expect( 'function_name' )
->once()
->andReturnUsing( function () {
return 'I am an alias!';
} );
$this->assertSame( 'I am an alias!', function_name() );
expect( 'function_name' )
->once()
->andThrow( 'RuntimeException' );
$this->expectException( 'RuntimeException' );
function_name();
andReturn( 'Baz!' )
— функция вернет Baz!;andReturn( 'One', 'Two' )
— функция вернет One в первый раз и Two во второй;andReturnValues( 'One', 'Two' )
— функция вернет One в первый раз и Two во второй;andReturnNull()
— функция вернет null;andReturnUsing( callback )
— функция вернет callback;andThrow( 'RuntimeException' )
— функция выбросит исключениеRuntimeException
.
Тестирование хуков
Тестирование подключений хуков
Тестируемый класс:
class Metabox {
public function hooks() {
add_action( 'init', [ $this, 'init' ], 20, 4 );
add_filter( 'the_title', [ $this, 'the_title' ], 20, 2 );
}
public function init() {
}
public function the_title() {
}
}
Проверить подключение хуков можно с помощью функций has_action
и has_filter
:
public function test_hooks() {
$metabox = new Metabox();
$metabox->hooks();
$this->assertTrue( has_action( 'init', [ $metabox, 'init' ] ) );
$this->assertTrue( has_filter( 'the_title', [ $metabox, 'the_title' ] ) );
}
Тестирование наличия хуков
Пример класса:
class Metabox {
public function awesome_method() {
do_action( 'my_action', $this );
return apply_filters( 'my_filter', 'Filter applied', $this );
}
}
Проверяем с помощью did_action
и applied
, которые возвращают кол-во вызовов данных хуков:
public function test_awesome_method() {
$metabox = new Metabox();
$this->assertNull( $metabox->awesome_method() );
$this->assertSame( 1, did_action( 'my_action' ) );
$this->assertSame( 1, applied( 'my_filter' ) );
}
Как видите из прошлого примера, то мы не проверили, с какими параметрами вызван хук и для фильтра не подменили результат.
Поэтому тестировать лучше и более качественее с помощью BrainMonkeyActionsexpectDone
и BrainMonkeyFiltersexpectApplied
:
public function test_awesome_method() {
$metabox = new Metabox();
expectDone( 'my_action' )
->once()
->with( $metabox );
expectApplied( 'my_filter' )
->once()
->with( 'Filter applied', $metabox )
->andReturn( 'Brain Monkey rocks!' );
$metabox->awesome_method();
}
Вывод
С помощью Brain Monkey мы можем тестировать большую часть функциональности WordPress. А как вы считаете что лучше WP_Mock или Brain Monkey?