31

So far for my Magento 2 testing needs, I've been using PHP Unit as (more or less) an acceptance tester -- testing results of server and HTML requests made into a system with my module(s) installed. I'd like to be able to create my own integration tests. Do the testing tools that ship with Magento 2 allow third party developers to create their own integration tests that leverage Magento's test framework code? Or will we all be rolling our own bootstrap?

That is

  1. I am a Magento developer
  2. I'd like to create an integration test
  3. I'd my integration test to have a fully bootstrap Magento environment to play in (i.e object manager and/or dependency injection to use)
  4. I'd like my integration test to extend the Magento\TestFramework\TestCase\AbstractController test so I have the same helpers as Magento tests
  5. I'd like to be able to run my tests in isolation from the rest of the test suite (i.e. not have to wait 2 hours to run my 15 seconds of tests)
  6. I'd like my tests stored separately from Magento's tests

The dev docs site has some starter articles on testing, but they seem oriented at running the tests that ship with Magento and not creating and running your own tests. There's the old sample modules, but they all extend the PHPUnit_Framework_TestCase class and seem to be unit tests (i.e. testing code that doesn't rely on the Magento framework)

Is there a Magento provided way of doing this?

If not, has anyone rolled their own setup in such a way that the test of the Magento developer community could adopt it as a standard?

3 Answers 3

23

This works for us but we haven't yet looked into moving them to a separate location to address 6.)

1.) Place your integration tests under dev/tests/integration/testsuite/Vendor
2.) copy dev/tests/integration/phpunit.dist.xml
to
dev/tests/integration/phpunit.xml

and replace

        <directory suffix="Test.php">testsuite</directory>
        <directory suffix="Test.php">../../../update/dev/tests/integration/testsuite</directory>
        <exclude>testsuite/Magento/Test/Integrity</exclude>
        <exclude>testsuite/Magento/MemoryUsageTest.php</exclude>

with

        <directory suffix="Test.php">testsuite/Vendor</directory>

3.) run it ../../../vendor/bin/phpunit or with ../../../vendor/bin/phpunit path/to/tests from the dev/test/integration folder

Please note that the integration tests take longer than 15 seconds, at least on first run as it essentially installs Magento. You can save on subsequent runs if you use

<const name="TESTS_CLEANUP" value="disabled"/>

in your phpunit.xml

11

I have played a little bit with the integration tests, and this is what I found so far.

Basically, I've followed similar steps to what Fooman said, with some differences in order to make the integration test be part of my module.

These are the steps I followed:

1- Place your integration tests under app/code/Vendor/CustomModule/Test/Integration

2- Copy dev/tests/integration/phpunit.dist.xml to dev/tests/integration/phpunit.xml

and replace

<testsuite name="Magento Integration Tests">
    <directory suffix="Test.php">testsuite</directory>
    <directory suffix="Test.php">../../../update/dev/tests/integration/testsuite</directory>
    <exclude>testsuite/Magento/Test/Integrity</exclude>
    <exclude>testsuite/Magento/MemoryUsageTest.php</exclude>
</testsuite>

with

<testsuite name="Magento Integration Tests">
    <directory suffix="Test.php">../../../app/code/Vendor/CustomModule/Test/Integration</directory>
</testsuite>

3- Then I run it using the CLI tool bin/magento dev:test:run integration

You should have in mind what Fooman says about the "TESTS_CLEANUP" and the time it takes to setup the integration tests if you have the cleanup enable.

Here I add a functional example for further reference. You will see how you can access the object manager, and generate instance of Magento classes, as well as using Magento fixtures.

app/code/Vendor/CustomModule/Controller/Order/Info.php

namespace Vendor\CustomModule\Controller\Order;

use Magento\Framework\Controller\ResultFactory;

class Info
    extends \Magento\Framework\App\Action\Action
{
    /**
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
     */
    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
    )
    {
        $this->orderRepository = $orderRepository;
        parent::__construct($context);
    }

    /**
     * Return Json OrderInfo
     *
     * @return \Magento\Framework\Controller\Result\Json $this
     */
    public function execute()
    {
        $orderId = $this->getRequest()->getParam('id');
        $order = $this->orderRepository->get($orderId);
        $orderInfo = [
            'total' => $order->getBaseGrandTotal()
        ];

        /** @var \Magento\Framework\Controller\Result\Json $result */
        $result = $this->resultFactory->create(ResultFactory::TYPE_JSON);
        return $result->setData($orderInfo);
    }

}

app/code/Vendor/CustomModule/etc/frontend/routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="vendor_custommodule" frontName="vendor_custommodule">
            <module name="Vendor_CustomModule"/>
        </route>
    </router>
</config>

app/code/Vendor/CustomModule/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_CustomModule" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Sales" />
        </sequence>
    </module>
</config>

app/code/Vendor/CustomModule/Test/Integration/Controller/Order/InfoTest.php

namespace Vendor\CustomModule\Controller\Order;

use Magento\TestFramework\TestCase\AbstractController;

class InfoTest extends AbstractController
{
    public function getOrderInfoActionDataProvider()
    {
        return [
            'order with one simple item' => [
                'incrementId' => '100000001',
                'contentType' => 'application/json',
                'orderTotal' => 100
            ]
        ];
    }

    /**
     * @dataProvider getOrderInfoActionDataProvider
     * @magentoDataFixture Magento/Sales/_files/order.php
     */
    public function testOrderInfoAction($incrementId, $expectedContentType, $expectedOrderTotal)
    {
        /** @var $objectManager \Magento\TestFramework\ObjectManager */
        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();

        /** @var \Magento\Sales\Model\OrderFactory $orderFactory */
        $orderFactory = $objectManager->get('Magento\Sales\Model\OrderFactory');
        $order = $orderFactory->create();
        $order->loadByIncrementId($incrementId);

        $this->dispatch("vendor_custommodule/order/info/id/{$order->getId()}");

        $contentType = $this->getResponse()->getHeader('Content-Type');
        $this->assertEquals($expectedContentType, $contentType->getFieldValue());

        $responseJson = $this->getResponse()->getBody();
        $responseArray = json_decode($responseJson, true);
        $this->assertEquals($expectedOrderTotal, $responseArray['total']);
    }
}

app/code/Vendor/CustomModule/registration.php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Vendor_CustomModule',
    __DIR__
);
1
  • Please note that your own solution is fine and it works, as long as you're using the fixtures of the Magento core. If you want to use your own fixtures, you will bump into the issues as discussed earlier. Commented Jul 16, 2019 at 6:41
11

I've successfully placed my integration tests into a separate directory: src/My/Module/test/integration. It could be any other directory as well, like app/code/My/Module/Test.

Add them as new test suite to the Magento integration tests: Copy dev/tests/integration/phpunit.xml.dist to dev/tests/integration/phpunit.xml and add the following in the <testsuites> node:

<testsuite name="My_Module">
    <directory suffix="Test.php">../../../src/My/Module/test</directory>
</testsuite>

Then run the tests like this from the dev/tests/integration directory:

../../../vendor/bin/phpunit --testsuite "My_Module"

With the --testsuite parameter you can select one test suite by name, so that not all integration tests are run at once

Update: Fixtures

To use own fixtures, a little workaround was necessary, because in Magento\TestFramework\Annotation the fixture base directory is defined globally. But fortunately Magento allows method names as fixtures as well, so the following works:

/**
 * @magentoDataFixture loadFixture
 */
public function testSomething()
{
}

public static function loadFixture()
{
    include __DIR__ . '/_files/something_fixture.php';
}
5
  • 1
    Wouldn't you run into problems when using the @magentoDataFixture here github.com/magento/magento2/blob/develop/dev/tests/integration/… especially when combining with a custom fixture from your module with a core one? Commented Mar 8, 2016 at 22:37
  • 1
    tbh I did not try it yet but it looks like a problem, yeah. Might be necessary to set the include path to make these fixtures work. Commented Mar 8, 2016 at 22:44
  • 1
    @KristofatFooman Found a solution for the fixtures, see update Commented Mar 11, 2016 at 11:13
  • Great solution. There might be a couple of flaws here. First of all, there is a typo - the __DIR__ should be followed by a slash (/_files). Second, the fixture is loaded from within the TestFramework so that the __DIR__ actually points to the TestFramework directory and not your own module. The ComponentRegistrar can be used for this: require $ObjectManager::getInstance()->get(ComponentRegistrar::class)->getPath('module', 'Foo_Bar').'/Test/Integration/_files/example.php'; Commented Jul 16, 2019 at 6:38
  • @JisseReitsma I just now saw your comment, thanks! 1. Fixed the typo! 2. No, DIR is a magic constant that's resolved on compilation (not on runtime) to the directory of the current file, just like FILE is resolved to the full path of the file. See: php.net/manual/en/language.constants.magic.php Commented Aug 27, 2021 at 7:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.