Vinai Kopp

Magento Expert, Developer & Trainer

  • 04. The Plugin Integration Test Kata

    March 7, 2016

    Mage2Katas

    Welcome to the plugin integration test kata!

    In this episode we will write an integration test that not only validates some configuration, but instead checks all parts working together produce the desired result.

    Integration tests provide a huge value and are essential to have in addition to the unit tests created while doing TDD.

    For this kata we will assume the plugin already exists and is configured. If you would like a reminder on the details that, please check out the previous 3 episodes.

    We are going to add a new test to the existing file

    app/code/Mage2Kata/Interceptor/Test/Integration/Plugin/CustomerRepositoryPluginIntegrationTest.php

    I created that file as part of the Plugin Config Kata.

    When writing integration tests I rarely try to reach all execution paths, that is what unit tests are there for. What I want to check with integration tests is that my class and any collaborators work together correctly.

    We will write a test to check that the external customer API is called for new customers when the active area is webapi_rest.
    This is my starting point:

    public function testTheExternalApiIsCalledWhenANewCustomerIsSaved()
    {
        $this->setArea(Area::AREA_WEBAPI_REST);
    }

    Our ingredients for writing this test are a customer repository instance and a customer instance that we can save.

    public function testTheExternalApiIsCalledWhenANewCustomerIsSaved()
    {
        $this->setArea(Area::AREA_WEBAPI_REST);
        /** @var CustomerRepositoryInterface $customerRepository */
        $customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class);
    }

    But what about the customer?
    The actual core code will be executed, so we have to provide all the data so the customer can be saved successfully.

    We could manually instantiate a custome model and set the required data on it of course, but I find it is easier to use a data fixture.

    Thankfully the core provides us with one we can use, using the PHPDoc @magentoDataFixture annotation.

    /**
     * @magentoDataFixture Magento/Customer/_files/customer.php
     */
    public function testTheExternalApiIsCalledWhenANewCustomerIsSaved()
    {
        $this->setArea(Area::AREA_WEBAPI_REST);
        /** @var CustomerRepositoryInterface $customerRepository */
        $customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class);
    }

    The data fixture annotation takes a file path relative to the directory dev/tests/integration/testsuiteas an argument.
    For this test we will use the fixture
    Magento/Customer/_files/customer.php.

    If we look into the file, we can see that a customer with the ID 1 and the email customer@example.com is created.
    Lets just load that one in our test, just so we can save it again to trigger our plugin.

    /**
     * @magentoDataFixture Magento/Customer/_files/customer.php
     */
    public function testTheExternalApiIsCalledWhenANewCustomerIsSaved()
    {
        $this->setArea(Area::AREA_WEBAPI_REST);
        /** @var CustomerRepositoryInterface $customerRepository */
        $customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class);
        $customer = $customerRepository->get('customer@example.com');
     
        $customerRepository->save($customer);
    }

    When we run this test, we get an error: the class Mage2Kata\Interceptor\Plugin\ExternalCustomerApi does not exist.

    The error happens because the object manager is instantiating our plugin, but that requires an object of the ExternalCustomerApi class, which we didn’t create because it doesn’t really matter for the kata.

    One option to solve this problem would be to create the class.

    However, that would require us to know more about the external API, and we are really lacking specifications here.
    So instead we will use a test double, and instruct the object manager to use that instad of a real ExternalCustomerApi instance.

    /**
     * @magentoDataFixture Magento/Customer/_files/customer.php
     */
    public function testTheExternalApiIsCalledWhenANewCustomerIsSaved()
    {
        $mockExternalApi = $this->getMock(ExternalCustomerApi::class, ['registerNewCustomer']);
        $this->objectManager->configure([ExternalCustomerApi::class => ['shared' => true]]);
        $this->objectManager->addSharedInstance($mockExternalApi, ExternalCustomerApi::class);
     
        $this->setArea(Area::AREA_WEBAPI_REST);
        /** @var CustomerRepositoryInterface $customerRepository */
        $customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class);
        $customer = $customerRepository->get('customer@example.com');
     
        $customerRepository->save($customer);
    }

    We are configuring the object manager to treat the external customer API as a shared instance.

    And then we add our mock as a shared instance, so it will be used to satisfy the dependency of our plugin when it is instantiated.

    This type of manipulation of the object manager configuration is often very useful during integration tests.

    If we rerun the test now it passes again, however, we haven’t added any assertion yet, so our test is can be considered to be risky.
    We don’t really know what is happening, we are simply assuming.

    Lets add an expectation to our external API that it’s method will be called.

    $mockExternalApi->expects($this->once())->method('registerNewCustomer');

    And finally our test fails.

    To make the test pass we have to simulate that the customer being saved is a new customer.

    We can do so by setting the customer ID to null and specifying a different email address, so we don’t get a duplicate email exception.

    /**
     * @magentoDataFixture Magento/Customer/_files/customer.php
     */
    public function testTheExternalApiIsCalledWhenANewCustomerIsSaved()
    {
        $mockExternalApi = $this->getMock(ExternalCustomerApi::class, ['registerNewCustomer']);
        $mockExternalApi->expects($this->once())->method('registerNewCustomer');
        $this->objectManager->configure([ExternalCustomerApi::class => ['shared' => true]]);
        $this->objectManager->addSharedInstance($mockExternalApi, ExternalCustomerApi::class);
     
        $this->setArea(Area::AREA_WEBAPI_REST);
        /** @var CustomerRepositoryInterface $customerRepository */
        $customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class);
        $customer = $customerRepository->get('customer@example.com');
     
        $customer->setId(null)
            ->setFirstname('Alice')
            ->setEmail('alica@example.com');
     
        $customerRepository->save($customer);
    }

    And with that the test is green again.

    One side note, because we are using a @magentoDataFixture annotation the test is automatically run inside a database transaction.
    That transaction is then automatically rolled back after the test.

    Because of that we don’t have to worry about cleaning up the fixture or the new customer record for Alice.

    In the theoretical case that we didn’t want to use a data fixture annotation, but still wanted the test to run inside of a transaction, we could use the @magentoDbIsolation enabled annotation to do just that.

    And this completes the plugin integration test kata. Go ahead and delete the code right now, so we can write it again.

    I hope you enjoyed it and maybe learned something.
    Please let me know if you have any feedback!
    You can find me on twitter, or leave a comment on youtube under the video.

    Happy coding and thanks for reading!

    comments powered by Disqus