El paquete ayuda a simular las funciones internas de PHP de la manera más simple posible. Utilice este paquete cuando necesite simular funciones como: time()
, str_contains()
, rand
, etc.
Instalación
Uso
Simulacros de tiempo de ejecución
Simulacro predefinido
Mezcla de dos formas anteriores
PHPUnidad 9
PHPUnit 10 y superior
Registrar una extensión PHPUnit
Registrar simulacros
Estado
Seguimiento de llamadas
Funciones de espacio de nombres globales
Implementación de funciones internas
Funciones internas
Restricciones
Proveedores de datos
El compositor requiere xepozz/internal-mocker --dev
La idea principal es bastante simple: registrar un oyente para PHPUnit y llamar primero a la extensión Mocker.
Crear nuevo archivo tests/MockerExtension.php
Pegue el siguiente código en el archivo creado:
<?phpdeclare(strict_types=1);espacio de nombres AppTests;use PHPUnitRunnerBeforeTestHook;use PHPUnitRunnerBeforeFirstTestHook;use XepozzInternalMockerMocker;use XepozzInternalMockerMockerState;la clase final MockerExtension implementa BeforeTestHook, BeforeFirstTestHook {función pública ejecutarBeforeFirstTest(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); Estado Mocker::saveState(); }función pública ejecutarBeforeTest(cadena $prueba): void{ Estado Mocker::resetState(); } }
Registre el gancho como extensión en phpunit.xml.dist
<extensiones> <extensión clase="AppTestsMockerExtension"/> </extensiones>
Crear nuevo archivo tests/MockerExtension.php
Pegue el siguiente código en el archivo creado:
<?phpdeclare(strict_types=1);espacio de nombres AppTests;use PHPUnitEventTestPreparationStarted;use PHPUnitEventTestPreparationStartedSubscriber;use PHPUnitEventTestSuiteStarted;use PHPUnitEventTestSuiteStartedSubscriber;use PHPUnitRunnerExtensionExtension;use PHPUnitEventRunnerExtensionFacade;use PHPUnitRunnerExtensionParameterCollection;use PHPUnitTextUIConfigurationConfiguration;use XepozzInternalMockerMocker;use XepozzInternalMockerMockerState;la clase final MockerExtension implementa la extensión {función pública bootstrap(Configuración $configuración, Fachada $fachada, ParameterCollection $parámetros): void{$fachada->registerSubscribers(nueva clase () implementa StartedSubscriber {función pública notificar(Iniciado $evento): void{ MockerExtension::cargar(); } }, la nueva clase implementa PreparationStartedSubscriber {función pública notificar(PreparationStarted $evento): void{ Estado Mocker::resetState(); } }, ); }carga de función estática pública(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); Estado Mocker::saveState(); } }
Registre el gancho como extensión en phpunit.xml.dist
<extensiones> <bootstrap class="AppTestsMockerExtension"/> </extensiones>
Aquí ha registrado una extensión que se llamará cada vez que ejecute ./vendor/bin/phpunit
.
De forma predeterminada, todas las funciones se generarán y guardarán en el archivo /vendor/bin/xepozz/internal-mocker/data/mocks.php
.
Anule el primer argumento del constructor Mocker
para cambiar la ruta:
$mocker = new Mocker('/ruta/a/tu/mocks.php');
El paquete admite algunas formas de simular funciones:
Simulacros de tiempo de ejecución
Simulacros predefinidos
Mezcla de dos formas anteriores
Si desea que su caso de prueba se utilice con una función simulada, debe registrarlo antes.
Regrese a MockerExtension::executeBeforeFirstTest
creado y edite la variable $mocks
.
$ se burla = [ ['namespace' => 'AppService','nombre' => 'hora', ], ];
Este simulacro representará cada llamada de time()
bajo el espacio de nombres AppService
a través de un contenedor generado.
Cuando desee simular el resultado de las pruebas, debe escribir el siguiente código en el caso de prueba necesario:
MockerState::addCondition( 'AppService', // espacio de nombres 'hora', // nombre de la función [], // argumentos 100 // resultado);
También puede utilizar una devolución de llamada para establecer el resultado de la función:
MockerState::addCondition( '', // espacio de nombres 'headers_sent', // nombre de la función [nulo, nulo], // ambos argumentos son referencias y aún no están inicializados en la llamada a la función fn (&$archivo, &$línea) => $archivo = $línea = 123, // resultado de la devolución de llamada);
Entonces su caso de prueba se verá así:
<?phpnamespace AppTests;use AppService;use PHPUnitFrameworkTestCase;la clase ServiceTest extiende TestCase {función pública testRun2(): void{$servicio = nuevo Servicio(); MockerState::addCondition('AppService','hora', [],100);$this->assertEquals(100, $servicio->doSomething()); } }
Vea el ejemplo completo en XepozzInternalMockerTestsIntegrationDateTimeTest::testRun2
Los simulacros predefinidos le permiten simular comportamientos globalmente.
Significa que no necesita escribir MockerState::addCondition(...)
en cada caso de prueba si desea simularlo para todo el proyecto.
Tenga en cuenta que las mismas funciones de diferentes espacios de nombres no son las mismas para
Mocker
.
Así que volvamos a la MockerExtension::executeBeforeFirstTest
creada y editemos la variable $mocks
.
$ se burla = [ ['namespace' => 'AppService','nombre' => 'hora','resultado' => 150,'argumentos' => [], ], ];
Después de esta variante, cada AppServicetime()
devolverá 150
.
Puedes agregar muchas simulaciones. Mocker
compara los valores arguments
con los argumentos de la función de llamada y devuelve el resultado necesario.
Mix significa que puedes usar el simulacro predefinido al principio y el simulacro en tiempo de ejecución después.
Si utiliza Runtime mock
puede enfrentar el problema de que después de simular la función, todavía se burla de ella en otros casos de prueba.
MockerState::saveState()
y MockerState::resetState()
resuelven este problema.
Estos métodos guardan el estado "actual" y descargan cada simulacro Runtime mock
que se aplicó.
El uso de MockerState::saveState()
después de Mocker->load($mocks)
guarda solo simulacros predefinidos .
Puede realizar un seguimiento de las llamadas de funciones simuladas utilizando el método MockerState::getTraces()
.
$traces = MockerState::getTraces('AppService', 'hora');
$traces
contendrá una serie de matrices con la siguiente estructura:
[ ['argumentos' => [], // argumentos de la función'trace' => [], // el resultado de la función debug_backtrace'result' => 1708764835, // resultado de la función],// ... ]
Todas las funciones internas están modificadas para que sean compatibles con las originales. Hace que las funciones utilicen argumentos referenciados ( &$file
) como lo hacen los originales.
Están ubicados en el archivo src/stubs.php
.
Si necesita agregar una nueva firma de función, anule el segundo argumento del constructor Mocker
:
$mocker = new Mocker(stubPath: '/ruta/a/tu/stubs.php');
La forma en que puedes burlarte de las funciones globales es deshabilitarlas en php.ini
: https://www.php.net/manual/en/ini.core.php#ini.disable-functions
La mejor manera es deshabilitarlos solo para pruebas ejecutando un comando con los indicadores adicionales:
php -ddisable_functions=${funciones} ./vendor/bin/phpunit
Si está utilizando PHPStorm, puede configurar el comando en la sección
Run/Debug Configurations
. Agregue la bandera-ddisable_functions=${functions}
al campoInterpreter options
.
Puede mantener el comando en el archivo
composer.json
en la secciónscripts
.
{ "scripts": {"test": "php -ddisable_functions=hora, serializar, encabezado, fecha ./vendor/bin/phpunit" } }
Reemplace
${functions}
con la lista de funciones que desea simular, separadas por comas, por ejemplo:time,rand
.
Ahora también puedes burlarte de funciones globales.
Cuando desactivas una función en php.ini
ya no puedes llamarla. Eso significa que debes implementarlo tú mismo.
Obviamente, casi todas las funciones implementadas en PHP tienen el mismo aspecto que las de Bash.
La forma más corta de implementar una función es usar la sintaxis `bash command`
:
$mocks[] = [ 'espacio de nombres' => '', 'nombre' => 'hora', 'función' => fn () => `fecha +%s`, ];
Tenga en cuenta que dejar una función global sin implementación provocará una llamada de recurso de la función, lo que provocará un error fatal.
A veces puede enfrentar una situación desagradable cuando la función simulada no se burla sin forzar el uso namespace
function
. Puede significar que está intentando crear un archivo de intérprete PHP en @dataProvider
. Tenga cuidado y, como solución alternativa, le sugiero que llame al simulacro en el constructor de la prueba. Entonces, primero mueva todo el código de su método de extensión executeBeforeFirstTest
al nuevo método estático y llámelo en los métodos executeBeforeFirstTest
y __construct
.
La clase final MyTest extiende PHPUnitFrameworkTestCase {función pública __construct(?cadena $nombre = nulo, matriz $datos = [], $nombredatos = '') {AppTestsMockerExtension::load();parent::__construct($nombre, $datos, $nombredatos); } ///...}
La clase final MockerExtension implementa BeforeTestHook, BeforeFirstTestHook {función pública ejecutarBeforeFirstTest(): void{self::load(); }carga de función estática pública(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); Estado Mocker::saveState(); }función pública ejecutarBeforeTest(cadena $prueba): void{ Estado Mocker::resetState(); } }
Todo eso gracias a PHPUnit 9.5 y un sistema de gestión de eventos inferior. La funcionalidad del proveedor de datos comienza a funcionar antes de cualquier evento, por lo que es imposible burlarse de la función al comienzo del tiempo de ejecución.