Подмена встроенных php-функций

Подмена встроенных php-функций

Часто при написании тестов надо протестировать внешнюю возможность или возможность, которая встроенная в php.

Рассмотрим для примера сохранение метаполей для записей в WordPress:

class Metabox { public function save( int $post_id) {$nonce = filter_input( INPUT_POST, '_nonce', FILTER_SANITIZE_STRING);if(! wp_verify_nonce( $nonce, 'very-secret-nonce')) {return;}$field = filter_input( INPUT_POST, 'field', FILTER_SANITIZE_STRING);update_post_meta( $post_id, 'field', $field); }}$metabox = new Metabox();add_action( 'save_post', [ $metabox, 'save' ]);

В данном примере мы имеем внешние ф-ции: wp_verify_nonce, update_post_meta; и встроенную ф-цию php — filter_input.

Если с внешними возможностями мы можем справится с помощью WP_Mock . То со встроенной функцией filter_input все сложнее т.к. она встроенная в php и заменить ее при помощи WP_Mock не получится. Но с данной проблемой без труда справится Function Mocker .

Если запустить юнит-тест и посмотреть, что будет в переменных $nonce и $field, то они будут null.

Установка Function Mocker

composer require lucatume/function-mocker:~1.0

Function Mocker в bootstrap.php

Сейчас необходимо подключить библиотеку в файле bootstrap.php:

use tad\FunctionMocker\FunctionMocker;//... FunctionMocker::init( [ 'whitelist'    => [            realpath( PLUGIN_PATH. '/src/),        ],        'blacklist'             => [            realpath( PLUGIN_PATH),        ],        'redefinable-internals' => [ 'filter_input' ],    ]);

Необходимо инициализровать библиотеку при помощи FunctionMocker::init и передать настройки:

  • whitelist — путь к папке, где лежат файлы проекта, которые вы будете тестировать;
  • blacklist — путь к файлам, где запрещается подмена возможностей;
  • redefinable-internals — массив возможностей, которые необходимо переопределить.

Фикстуры для Function Mocker

Последним подготовительным этапом для начала тестирования данное добавление
фикстур :

<?phpuse PHPUnit\Framework\TestCase;use tad\FunctionMocker\FunctionMocker;class Test_Metabox extends TestCase {public function setUp() {FunctionMocker::setUp();parent::setUp();}public function tearDown() {parent::tearDown();FunctionMocker::tearDown();}}

Пример теста

Сейчас начинаем писать сам тест:

<?phpuse PHPUnit\Framework\TestCase;use tad\FunctionMocker\FunctionMocker;class Test_Metabox extends TestCase {public function setUp() {FunctionMocker::setUp();parent::setUp();}public function tearDown() {parent::tearDown();FunctionMocker::tearDown();}public function test_save() {$nonce = 'nonce';FunctionMocker::replace( 'filter_input', $nonce);FunctionMocker::replace( 'wp_verify_nonce', true);FunctionMocker::replace( 'update_post_meta', true);$metabox = new Metabox();$metabox->save( 10);}}

Сейчас все вызовы в тестовом способе filter_input возвращают строку nonce, а вызовы wp_verify_nonce и update_post_metatrue. Но нам этого малого, для хорошего теста. В коде у нас пару раз вызывается filter_input и нам надо приобрести различные ответы:

<?phpuse PHPUnit\Framework\TestCase;use tad\FunctionMocker\FunctionMocker;class Test_Metabox extends TestCase {public function test_save() {$nonce = 'nonce';$field = 'some-text-field';FunctionMocker::replace('filter_input',function() use( $nonce, $field) {static $i = 0;$answers = [ $nonce, $field ];return $answers[ $i ++ ];});                // Or in latest version                // FunctionMocker::replaceInOrder( 'filter_input', [ $nonce, $field ]);FunctionMocker::replace( 'wp_verify_nonce', true);FunctionMocker::replace( 'update_post_meta', true);$metabox = new Metabox();$metabox->save( 10 );}}

Сейчас, при помощи анонимной возможности возвращаем при первом вызове строку nonce , а при втором some-text-field . В более поздних версиях можно без проблем это сделать более кратко при помощи FunctionMocker::replaceInOrder .

Осталось проверить, какие настройки мы передаем в возможности:

<?phpuse PHPUnit\Framework\TestCase;use tad\FunctionMocker\FunctionMocker;class Test_Metabox extends TestCase {public function setUp() {FunctionMocker::setUp();parent::setUp();}public function tearDown() {parent::tearDown();FunctionMocker::tearDown();}public function test_save() {$post_id          = 10;$nonce            = 'nonce';$field            = 'some-text-field';$filter_input     = FunctionMocker::replace('filter_input',function () use ( $nonce, $field ) {static $i = 0;$answers = [ $nonce, $field ];return $answers[ $i ++ ];});$wp_verify_nonce  = FunctionMocker::replace( 'wp_verify_nonce', true );$update_post_meta = FunctionMocker::replace( 'update_post_meta', true );$metabox          = new Metabox();$metabox->save( $post_id );$filter_input->wasCalledWithOnce( [ INPUT_POST, '_nonce', FILTER_SANITIZE_STRING ] );$filter_input->wasCalledWithOnce( [ INPUT_POST, 'field', FILTER_SANITIZE_STRING ] );$wp_verify_nonce->wasCalledWithOnce( [ $nonce, 'very-secret-nonce' ] );$update_post_meta->wasCalledWithOnce( [ $post_id, 'field', $field ] );}}

Если вы сделали все правильно, то вы получили успешный тест 🙂 и опыт для тестирования внешних возможностей и встроенных в php.

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

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