Magento 2: Beheben des Aufrufs der undefinierten Methode Mock_BlockFactory_4b440480 :: create () Testfehler

7

Ich habe kürzlich eine Pull- Anfrage an Magento gesendet, um eine einzelne Instanz eines Objektmanagers zu reparieren, der direkt verwendet wird.

Der Testlauf der Travis-Einheit von Magento schlug jedoch mit dem folgenden Fehler fehl .

Schwerwiegender PHP-Fehler: Aufruf der undefinierten Methode Mock_BlockFactory_4b440480 :: create () in /home/travis/build/magento/magento2/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php in Zeile 39

Anhand des Travis-Builds kann ich nicht einmal sagen, welcher Test fehlgeschlagen ist. Ich konnte lokal einen ähnlichen (identischen?) Fehler mit einem Stack-Trace erhalten

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

Ich konnte den lokalen Fehler auf diesen Test eingrenzen - aber ich bin ein bisschen ratlos, was los ist.

Ich vermute, dass das Test-Framework automatisch ein DI-Argument für mich verspottet hat, aber dass beim automatischen Verspotten die createMethode fehlt . Wenn das der Fall ist, dann ist meine eigentliche Frage Wie füge ich ein Mock für eine neu injiziert Abhängigkeit im Test - Framework von Magento .

Ich bin jedoch noch nie so tief in Magentos Testkaninchen gegangen, daher bin ich mir nicht sicher, was hier tatsächlich passieren muss. Kann mich jemand mit Magento-Testerfahrung klarstellen?

Alan Storm
quelle
Ich denke, Sie müssen die Fabrik und das zurückgegebene Objekt verspotten create, genau wie hier .
Nevvermind

Antworten:

11

Der kann \Magento\Framework\TestFramework\Unit\Helper\ObjectManagernicht automatisch ein Factory-Mock erstellen.
(Nebenbei bemerkt, ich benutze das nie \Magento\Framework\TestFramework\Unit\Helper\ObjectManager, da ich versuche, die Menge an Magie in Unit-Tests auf ein Minimum zu beschränken.)

Die folgenden Änderungen sind erforderlich, um den Test zu bestehen:

Zunächst erstellen Sie das Mock für den Modellblock Fabrik im Setup:

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

Oder kompakter:

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

Zweitens fügen Sie den neuen Mock zu den Konstruktorargumenten des Controllers hinzu:

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

Drittens aktualisieren Sie die Tests so, dass nicht mehr erwartet wird, dass das BlockModell vom Scheinobjektmanager, sondern von der neuen Factory erstellt wird:

Ersetzen

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

mit

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

Danach besteht der Test.

Zusätzliche Reinigung

Die folgenden Punkte haben nichts mit Ihrer Frage zu tun, aber ich kann trotzdem nicht widerstehen, sie aufzuschreiben.

Die $objectManagerMockEigenschaft ist jetzt veraltet und alle Verweise darauf können ( sollten ) aus der Testklasse entfernt werden.

Als nächstes ist seit PHP 5.5 die ::classKonstante verfügbar. Dies ist der Verwendung von Zeichenfolgen für Klassennamen vorzuziehen, da dies das automatische Refactoring in der IDE und das Auffinden der Verwendung einer bestimmten Klasse unterstützt. Es macht PHPStorm schlauer. Also würde ich alle Stringklassennamen durch die Konstante ersetzen, zB 'Magento\Framework\App\RequestInterface'durch \Magento\Framework\App\RequestInterface::class.

Auch frage ich die Verwendung von \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.
Meiner Meinung nach ist es besser, die zu testende Klasse manuell mit zu instanziieren new. Das einzige, was der Helfer zu diesem Zeitpunkt tut, ist, dass er einen \Magento\Framework\RegistrySchein erstellt. Ich würde das lieber selbst erstellen und als Konstruktorargument angeben. Auf diese Weise werden beim Lesen des Testcodes alle Abhängigkeiten deutlich.

Die nächste Bereinigung ist ziemlich wichtig. Ich würde die Methoden Unit - Test ändern , um nicht die Umsetzung genau widerspiegeln.
Nehmen Sie zum Beispiel das Setup des Anforderungsmodells in testDeleteActionThrowsException:

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

Ist es wirklich wichtig, wie oft getParamangerufen wird? Sollte der Test fehlschlagen, wenn er zweimal aufgerufen wird oder überhaupt nicht? Ich denke, das ist nicht wichtig, solange wir das Endergebnis der Methode testen, was wir erwarten.
Eine engere Bindung des Testcodes an die Implementierung als erforderlich führt zu starren Tests, die schwieriger zu warten sind.
Also dieses Beispiel würde ich umgestalten

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

Und schließlich, da dies expects($this->any())die Standardeinstellung ist, ist es gut, diese zu entfernen, um die Unordnung zu verringern.

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

Das liest sich viel schöner.

Möglicherweise ist es sinnvoll, den erwarteten Parameter getParamin diesem Test anzugeben , obwohl der ursprüngliche Testautor ihn weggelassen hat.

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

So würde ich wahrscheinlich den Test verlassen und weitermachen.

Ein weiterer Gedanke: Das Problem bei Getter-Methoden wie getParamist, dass, wenn ein Aufrufer versucht, auf verschiedene Werte zuzugreifen, der Mock verschiedene Dinge basierend auf dem Argumentwert zurückgeben muss.
Solche Änderungen in der Zukunft sind sehr wahrscheinlich, daher gebe ich manchmal eine Rückgabewertzuordnung an, selbst wenn es nur einen Wert gibt. Dies macht es einfach, den Test beizubehalten, wenn sich die zu testende Klasse in Zukunft ändert.

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

Falls Sie mit PHPUnit-Rückgabewertzuordnungen nicht vertraut sind, ist der nullWert im Array der optionale zweite Parameter für getParam($key, $defaultValue = null).

Vinai
quelle