Mocking Dependency¶
Given you have an application service interface like this:
<?php
namespace Acme\Awesome\Config;
interface ConfigProviderInterface
{
public function isFreeDeliverEnabled(): bool;
public function getFreeDeliveryThreshold(): float;
}
And you have an implementation for this service:
<?php
namespace Acme\Awesome\Config;
use Magento\Framework\App\Config\ScopeConfigInterface;
class ConfigProvider implements ConfigProviderInterface
{
/** @var ScopeConfigInterface */
private $scopeConfig;
public function __construct(ScopeConfigInterface $scopeConfig)
{
$this->scopeConfig = $scopeConfig;
}
public function isFreeDeliverEnabled(): bool
{
return $this->scopeConfig->isSetFlag('path/to/config');
}
public function getFreeDeliveryThreshold(): float
{
return (float) $this->scopeConfig->getValue('path/to/another/config');
}
}
And you have the following DI config to mark this implementation as the default implementation:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Acme\Awesome\Config\ConfigProviderInterface" type="Acme\Awesome\Config\ConfigProvider" />
</config>
In addition to these you have an application service which depends on this config interface, e.g.:
<?php
namespace Acme\Awesome\Service;
use Acme\Awesome\Config\ConfigProviderInterface;
class DeliveryCostCalculator
{
private const DELIVERY_COST = 5.0;
/** @var ConfigProviderInterface */
private $deliveryConfig;
public function __construct(ConfigProviderInterface $deliveryConfig)
{
$this->deliveryConfig = $deliveryConfig;
}
public function calculate(float $total): float
{
if ($this->isFreeDelivery($total)) {
return 0.0;
}
return self::DELIVERY_COST;
}
private function isFreeDelivery(float $total): bool
{
if (!$this->deliveryConfig->isFreeDeliverEnabled()) {
return false;
}
return $total >= $this->deliveryConfig->getFreeDeliveryThreshold();
}
}
When you write your application tests, if you would like to avoid relying on the database, then you either need to mock Magento\Framework\App\Config\ScopeConfigInterface
or Acme\Awesome\Config\ConfigProviderInterface
. Lets assume we would like to mock our own ConfigProviderInterface
this time.
First of all we need to configure a test
area in Magento.
We can do this by adding the following to the module’s global etc/di.xml
:
<?xml version="1.0" encoding="utf-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\App\AreaList">
<arguments>
<argument name="areas" xsi:type="array">
<item name="test" xsi:type="null" />
</argument>
</arguments>
</type>
</config>
Or we can simply install the Test area Magento 2 module which will define an area called test
in the same way. :)
Now we can define our DI overrides in the module’s etc/test/di.xml
.
It will look like this:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Acme\Awesome\Config\ConfigProviderInterface" type="Acme\Awesome\Test\FakeConfigProvider" />
</config>
And we are done. After a cache clear everything should be ready to use. If you inject the Acme\Awesome\Service\DeliveryCostCalculator
into your Behat Context then it will use the Acme\Awesome\Test\FakeConfigProvider
which we can freely modify in our tests.
E.g.:
FakeConfigProvider:
<?php
namespace Acme\Awesome\Test;
use Acme\Awesome\Config\ConfigProviderInterface;
class FakeConfigProvider implements ConfigProviderInterface
{
/** @var bool */
private $isFreeDeliveryEnabled = false;
/** @var float */
private $freeDeliveryThreshold = 0.0;
public function isFreeDeliverEnabled(): bool
{
return $this->isFreeDeliveryEnabled;
}
public function getFreeDeliveryThreshold(): float
{
return $this->freeDeliveryThreshold;
}
public function enableFreeDelivery(): void
{
$this->isFreeDeliveryEnabled = true;
}
public function disableFreeDelivery(): void
{
$this->isFreeDeliveryEnabled = false;
}
public function setFreeDeliveryThreshold(float $threshold): void
{
$this->freeDeliveryThreshold = $threshold;
}
}
behat.yml: In order to load this custom DI configuration during the test run the test area need to be configured in the Behat test suite so it can load to merge it with the default area.
default:
suites:
yoursuite:
autowire: true
contexts:
- YourContext
services: '@bex.magento2_extension.service_container'
magento:
area: test
Feature:
Feature: Delivery Cost Calculation
Scenario: Standard Delivery applies when under the configured threshold
Given The the cart total is "98.99"
And The free delivery is enabled
And The free delivery cost threshold is configured to "100"
When The delivery total is calculated
Then The delivery cost is "5.0"
Scenario: Free Delivery applies when above the configured threshold
Given The the cart total is "120"
And The free delivery is enabled
And The free delivery cost threshold is configured to "100"
When The delivery total is calculated
Then The delivery cost is "0.0"
Feature Context:
<?php
use Behat\Behat\Context\Context;
use Acme\Awesome\Service\DeliveryCostCalculator;
use Acme\Awesome\Test\FakeConfigProvider;
use PHPUnit\Framework\Assert;
class FeatureContext implements Context
{
/** @var DeliveryCostCalculator */
private $deliveryCostCalculator;
/** @type float|null */
private $cartTotal = null;
/** @type float|null */
private $deliveryCost = null;
public function __construct(DeliveryCostCalculator $deliveryCostCalculator)
{
$this->deliveryCostCalculator = $deliveryCostCalculator;
}
/**
* @Given The the cart total is :total
*/
public function theCartContainsTheFollowingItems(float $total)
{
$this->cartTotal = $total;
}
/**
* @Given The free delivery is enabled
*/
public function theFreeDeliveryIsEnabled(FakeConfigProvider $deliveryConfig)
{
$deliveryConfig->enableFreeDelivery();
}
/**
* @Given The free delivery is disabled
*/
public function theFreeDeliveryIsDisabled(FakeConfigProvider $deliveryConfig)
{
$deliveryConfig->disableFreeDelivery();
}
/**
* @Given The free delivery cost threshold is configured to :threshold
*/
public function theFreeDeliveryCostThresholdIsConfiguredTo(float $threshold, FakeConfigProvider $deliveryConfig)
{
$deliveryConfig->setFreeDeliveryThreshold($threshold);
}
/**
* @When The delivery total is calculated
*/
public function theDeliveryTotalIsCalculated()
{
$this->deliveryCost = $this->deliveryCostCalculator->calculate($this->cartTotal);
}
/**
* @Then The delivery cost is :expectedDeliveryCost
*/
public function theDeliveryCostIs(float $expectedDeliveryCost)
{
Assert::assertEquals($expectedDeliveryCost, $this->deliveryCost);
}
}