O pacote ajuda a simular funções internas do php da maneira mais simples possível. Use este pacote quando precisar simular funções como: time()
, str_contains()
, rand
, etc.
Instalação
Uso
Simulações de tempo de execução
Simulação predefinida
Mistura de duas formas anteriores
Unidade PHP 9
PHPUnit 10 e superior
Registre uma extensão PHPUnit
Registrar simulações
Estado
Rastreamento de chamadas
Funções com namespace global
Implementação de função interna
Funções internas
Restrições
Provedores de dados
compositor requer xepozz/internal-mocker --dev
A ideia principal é bem simples: registrar um Listener no PHPUnit e chamar primeiro a extensão Mocker.
Crie um novo arquivo tests/MockerExtension.php
Cole o seguinte código no arquivo criado:
<?phpdeclare(strict_types=1);namespace AppTests;use PHPUnitRunnerBeforeTestHook;use PHPUnitRunnerBeforeFirstTestHook;use XepozzInternalMockerMocker;use XepozzInternalMockerMockerState;classe final MockerExtension implementa BeforeTestHook, BeforeFirstTestHook {função pública executeBeforeFirstTest(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); }função pública executeBeforeTest(string $teste): void{ MockerState::resetState(); } }
Registre o gancho como extensão em phpunit.xml.dist
<extensões> <extension class="AppTestsMockerExtension"/> </extensões>
Crie um novo arquivo tests/MockerExtension.php
Cole o seguinte código no arquivo criado:
<?phpdeclare(strict_types=1);namespace AppTests;use PHPUnitEventTestPreparationStarted;use PHPUnitEventTestPreparationStartedSubscriber;use PHPUnitEventTestSuiteStarted;use PHPUnitEventTestSuiteStartedSubscriber;use PHPUnitRunnerExtensionExtension;use PHPUnitRunnerExtensionFacade;use PHPUnitRunnerExtensionParameterCollection;use PHPUnitTextUIConfigurationConfiguration;use XepozzInternalMockerMocker;use XepozzInternalMockerMockerState;classe final MockerExtension implementa extensão {public function bootstrap(Configuração $configuration, Facade $facade, ParameterCollection $parameters): void{$facade->registerSubscribers(nova classe () implementa StartedSubscriber {public function notify(Started $event): void{ MockerExtension::load(); } }, nova classe implementa PreparationStartedSubscriber {public function notify(PreparationStarted $event): void{ MockerState::resetState(); } }, ); }função estática pública load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); } }
Registre o gancho como extensão em phpunit.xml.dist
<extensões> <bootstrap class="AppTestsMockerExtension"/> </extensões>
Aqui você registrou a extensão que será chamada sempre que você executar ./vendor/bin/phpunit
.
Por padrão, todas as funções serão geradas e salvas no arquivo /vendor/bin/xepozz/internal-mocker/data/mocks.php
.
Substitua o primeiro argumento do construtor Mocker
para alterar o caminho:
$mocker = new Mocker('/caminho/para/seu/mocks.php');
O pacote oferece suporte a algumas maneiras de simular funções:
Simulações de tempo de execução
Simulações predefinidas
Mistura de duas formas anteriores
Se você quiser fazer com que seu caso de teste seja usado com função simulada, você deve registrá-lo antes.
Volte ao MockerExtension::executeBeforeFirstTest
criado e edite a variável $mocks
.
$ zombarias = [ ['namespace' => 'AppService','nome' => 'hora', ], ];
Esta simulação fará proxy de cada chamada de time()
no namespace AppService
por meio de um wrapper gerado.
Quando você quiser simular resultados em testes, você deve escrever o seguinte código no caso de teste necessário:
MockerState::addCondition( 'AppService', // namespace 'tempo', // nome da função [], // argumentos 100 //resultado);
Você também pode usar um retorno de chamada para definir o resultado da função:
MockerState::addCondition( '', // espaço para nome 'headers_sent', // nome da função [null, null], // ambos os argumentos são referências e ainda não foram inicializados na chamada da função fn (&$file, &$line) => $file = $line = 123, // resultado do retorno de chamada);
Portanto, seu caso de teste ficará parecido com o seguinte:
<?phpnamespace AppTests;use AppService;use PHPUnitFrameworkTestCase;class ServiceTest estende TestCase {função pública testRun2(): void{$service = new Service(); MockerState::addCondition('AppService','tempo', [],100);$this->assertEquals(100, $service->doSomething()); } }
Veja o exemplo completo em XepozzInternalMockerTestsIntegrationDateTimeTest::testRun2
Simulações predefinidas permitem simular comportamento globalmente.
Isso significa que você não precisa escrever MockerState::addCondition(...)
em cada caso de teste se quiser simular todo o projeto.
Tenha em mente que as mesmas funções de namespaces diferentes não são as mesmas para
Mocker
.
Então, volte ao MockerExtension::executeBeforeFirstTest
criado e edite a variável $mocks
.
$ zombarias = [ ['namespace' => 'AppService','name' => 'time','result' => 150,'arguments' => [], ], ];
Após esta variante, cada AppServicetime()
retornará 150
.
Você pode adicionar muitas simulações. Mocker
compara os valores arguments
com os argumentos da função de chamada e retorna o resultado necessário.
Mix significa que você pode usar a simulação predefinida primeiro e a simulação do tempo de execução depois.
Se você usar Runtime mock
poderá enfrentar o problema de que, após a simulação da função, você ainda a terá ridicularizada em outros casos de teste.
MockerState::saveState()
e MockerState::resetState()
resolvem esse problema.
Esses métodos salvam o estado "atual" e descarregam cada simulação Runtime mock
que foi aplicada.
Usar MockerState::saveState()
após Mocker->load($mocks)
salva apenas simulações predefinidas .
Você pode rastrear chamadas de funções simuladas usando o método MockerState::getTraces()
.
$traces = MockerState::getTraces('AppService', 'tempo');
$traces
conterá um array de arrays com a seguinte estrutura:
[ ['arguments' => [], // argumentos da função'trace' => [], // o resultado de debug_backtrace function'result' => 1708764835, // resultado da função],// ... ]
Todas as funções internas são fragmentadas para serem compatíveis com as originais. Faz com que as funções usem argumentos referenciados ( &$file
) como os originais.
Eles estão localizados no arquivo src/stubs.php
.
Se você precisar adicionar uma nova assinatura de função, substitua o segundo argumento do construtor Mocker
:
$mocker = new Mocker(stubPath: '/caminho/para/seu/stubs.php');
A maneira como você pode simular funções globais é desativá-las no php.ini
: https://www.php.net/manual/en/ini.core.php#ini.disable-functions
A melhor maneira é desativá-los apenas para testes, executando um comando com flags adicionais:
php -ddisable_functions=${funções} ./vendor/bin/phpunit
Se você estiver usando o PHPStorm, você pode definir o comando na seção
Run/Debug Configurations
. Adicione o sinalizador-ddisable_functions=${functions}
ao campoInterpreter options
.
Você pode manter o comando no arquivo
composer.json
na seçãoscripts
.
{ "scripts": {"teste": "php -ddisable_functions=time,serialize,header,date ./vendor/bin/phpunit" } }
Substitua
${functions}
pela lista de funções que você deseja simular, separadas por vírgulas, por exemplo:time,rand
.
Agora você também pode simular funções globais.
Quando você desabilita uma função no php.ini
você não pode mais chamá-la. Isso significa que você deve implementá-lo sozinho.
Obviamente, quase todas as funções implementadas em PHP são iguais às do Bash.
A maneira mais curta de implementar uma função é usar a sintaxe `bash command`
:
$ mocks [] = [ 'namespace' => '', 'nome' => 'hora', 'função' => fn () => `data +%s`, ];
Tenha em mente que deixar uma função global sem implementação causará uma chamada de recurso da função, o que levará a um erro fatal.
Às vezes, você pode enfrentar uma situação desagradável quando a função simulada não é simulada sem o uso forçado namespace
function
. Isso pode significar que você está tentando criar um arquivo interpretador PHP em @dataProvider
. Tenha cuidado com isso e como solução alternativa, sugiro que você chame o zombador no construtor do teste. Portanto, primeiro mova todo o código do seu método de extensão executeBeforeFirstTest
para o novo método estático e chame-o nos métodos executeBeforeFirstTest
e __construct
.
classe final MyTest estende PHPUnitFrameworkTestCase {função pública __construct(?string $nome = null, array $data = [], $dataName = '') {AppTestsMockerExtension::load();parent::__construct($nome, $dados, $dataName); } /// ...}
classe final MockerExtension implementa BeforeTestHook, BeforeFirstTestHook {função pública executeBeforeFirstTest(): void{self::load(); }função estática pública load(): void{$mocks = [];$mocker = new Mocker();$mocker->load($mocks); MockerState::saveState(); }função pública executeBeforeTest(string $teste): void{ MockerState::resetState(); } }
Tudo isso por causa do PHPUnit 9.5 e do sistema de gerenciamento de eventos inferior. A funcionalidade do Data Provider começa a funcionar antes de qualquer evento, portanto é impossível simular a função no início do tempo de execução.