Magento 2: Corrigindo a chamada para o método indefinido Falhas no teste de Mock_BlockFactory_4b440480 :: create ()


7

Recentemente, enviei uma solicitação pull ao Magento para corrigir uma única instância de um gerenciador de objetos sendo usado diretamente.

No entanto, o teste de unidade travis do Magento falhou com o seguinte erro .

Erro fatal do PHP: chame o método indefinido Mock_BlockFactory_4b440480 :: create () em /home/travis/build/magento/magento2/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php na linha 39

Com base na construção do travis, nem sei dizer qual teste falhou. Consegui obter um erro semelhante (idêntico?) Localmente com um rastreamento de pilha

PHP Fatal error:  Call to undefined method Mock_BlockFactory_ec77572c::create() in /Users/alanstorm/Documents/github/astorm/magento2/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php on line 39
PHP Stack trace:
PHP   1. {main}() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/phpunit:0
PHP   2. PHPUnit_TextUI_Command::main() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/phpunit:55
PHP   3. PHPUnit_TextUI_Command->run() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/TextUI/Command.php:132
PHP   4. PHPUnit_TextUI_TestRunner->doRun() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/TextUI/Command.php:179
PHP   5. PHPUnit_Framework_TestSuite->run() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:426
PHP   6. PHPUnit_Framework_TestSuite->run() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestSuite.php:675
PHP   7. PHPUnit_Framework_TestCase->run() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestSuite.php:675
PHP   8. PHPUnit_Framework_TestResult->run() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestCase.php:753
PHP   9. PHPUnit_Framework_TestCase->runBare() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestResult.php:686
PHP  10. PHPUnit_Framework_TestCase->runTest() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestCase.php:817
PHP  11. ReflectionMethod->invokeArgs() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestCase.php:951
PHP  12. Magento\Cms\Test\Unit\Controller\Adminhtml\Block\DeleteTest->testDeleteAction() /Users/alanstorm/Documents/github/astorm/magento2/vendor/phpunit/phpunit/src/Framework/TestCase.php:951
PHP  13. Magento\Cms\Controller\Adminhtml\Block\Delete->execute() /Users/alanstorm/Documents/github/astorm/magento2/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/DeleteTest.php:151

Consegui restringir a falha local a este teste - mas estou com uma certa perda no que está acontecendo.

Meu palpite é que a estrutura de teste zombou automaticamente de um argumento de DI para mim, mas que a zombaria automática está faltando o createmétodo. Se for esse o caso, então a minha pergunta real é: Como adiciono uma simulação para uma dependência recém-injetada na estrutura de teste do Magento .

No entanto, eu nunca fui tão fundo no buraco de coelho de teste do Magento, então não tenho certeza do que realmente precisa acontecer aqui. Alguém com experiência em testes do Magento pode me esclarecer?


Acho que você precisará zombar da fábrica e do objeto retornado create, exatamente como aqui .
26416 nevvermind

Respostas:


11

O \Magento\Framework\TestFramework\Unit\Helper\ObjectManagernão é capaz de criar automaticamente uma simulação de fábrica.
(Em uma nota lateral, eu nunca uso o \Magento\Framework\TestFramework\Unit\Helper\ObjectManager, pois tento manter a quantidade de mágica em testes de unidade no mínimo.)

As seguintes alterações são necessárias para fazer o teste passar:

Primeiro , crie a simulação para a fábrica de blocos de modelos na configuração:

$this->modelBlockFactoryMock = $this->getMockBuilder(\Magento\Cms\Model\BlockFactory::class)
    ->disableOriginalConstructor()
    ->setMethods(['create'])
    ->getMock();

Ou, mais compacto:

$this->modelBlockFactoryMock = $this->getMock(\Magento\Cms\Model\BlockFactory::class, ['create'], [], '', false);

Segundo , adicione o novo mock aos argumentos do construtor dos controladores:

$this->deleteController = $this->objectManager->getObject(
    'Magento\Cms\Controller\Adminhtml\Block\Delete',
    [
        'context' => $this->contextMock,
        'modelBlockFactory' => $this->modelBlockFactoryMock
    ]
);

Terceiro , atualize os testes para que não esperem mais que o Blockmodelo seja criado pelo gerenciador de objetos simulados, mas pela nova fábrica:

Substituir

$this->objectManagerMock->expects($this->once())
    ->method('create')
    ->with('Magento\Cms\Model\Block')
    ->willReturn($this->blockMock);

com

$this->modelBlockFactoryMock->expects($this->once())
    ->method('create')
    ->willReturn($this->blockMock);

Depois disso, o teste passa.

Limpeza adicional

Os pontos a seguir não têm nada a ver com sua pergunta, mas não consigo resistir a escrevê-los.

A $objectManagerMockpropriedade agora está obsoleta e todas as referências a ela podem (na verdade, devem ) ser removidas da classe de teste.

A seguir, desde o PHP 5.5, a ::classconstante está disponível. É preferível usar strings para nomes de classes, pois ajuda na refatoração automática no IDE e na localização de usos de uma determinada classe. Isso torna o PHPStorm mais inteligente. Assim, gostaria de substituir todos os nomes de classe string com o constante, por exemplo, 'Magento\Framework\App\RequestInterface'com \Magento\Framework\App\RequestInterface::class.

Além disso, eu questiono o uso de \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.
Na minha opinião, é melhor instanciar a classe em teste manualmente usando new. A única coisa que o ajudante faz nesse momento é criar uma \Magento\Framework\Registryzombaria. Prefiro criar isso sozinho e especificá-lo como um argumento construtor. Dessa forma, todas as dependências são claras ao ler o código de teste.

A próxima limpeza é bastante importante. Eu mudaria os métodos de teste de unidade para não refletir exatamente a implementação.
Por exemplo, considere a configuração da solicitação simulada em testDeleteActionThrowsException:

$this->requestMock->expects($this->once())
    ->method('getParam')
    ->willReturn($this->blockId);

Realmente importa quantas vezes getParamé chamado? O teste deve falhar se for chamado duas vezes ou não for o caso? Eu acho que isso não é importante, desde que testemos o resultado final do método é o que esperamos.
Vincular o código de teste mais de perto à implementação então necessária leva a testes rígidos que são mais difíceis de manter.
Então, neste exemplo, eu refatoraria para

$this->requestMock->expects($this->any())
    ->method('getParam')
    ->willReturn($this->blockId);

E, finalmente, como expects($this->any())é o padrão, é bom removê-lo para reduzir a quantidade de lixo.

$this->requestMock->method('getParam')->willReturn($this->blockId);

Isso parece muito melhor.

Pode-se argumentar que pode fazer sentido especificar o parâmetro esperado getParamnesse teste, mesmo que o autor do teste original o tenha omitido.

$this->requestMock->method('getParam')
    ->with('block_id')
    ->willReturn($this->blockId);

Provavelmente é assim que eu deixaria o teste e seguiria em frente.

Mais um pensamento: o problema com os métodos getter getParamé que, se um chamador tenta acessar valores diferentes, o mock deve retornar coisas diferentes com base no valor do argumento.
Tais mudanças no futuro são bastante prováveis, portanto, às vezes, eu especifico um mapa de valor de retorno, mesmo se houver apenas um valor. Isso facilita a manutenção do teste quando a classe que está sendo testada mudar no futuro.

$this->requestMock->method('getParam')
    ->willReturnMap([
        ['block_id', null, $this->blockId]
    ]);

Caso você não esteja familiarizado com os mapas de valores de retorno PHPUnit, o nullvalor na matriz é o segundo parâmetro opcional para getParam($key, $defaultValue = null).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.