Imagine you have a helper class that is responsible for downloading and processing JSON files. This is something you don’t want to test manually all the time. I didn’t implement many unit tests in the past, simply because it wasn’t necessary or just not scoped in a project. However, there are cases where you actually save a lot of valuable time with just a few simple unit tests.
In this article I would like to show you how to inject store configurations ( ScopeConfigInterface ) to your mocked classes which are necessary to test multiple scenarios. In the following example I am going to test a helper class and make sure the following scenarios are working as expected.
- Is the module enabled in Stores > Configuration > General?
- Is a URL defined?
- Does the URL return a correct HTTP response?
- Is the downloaded content a valid JSON string?
1 2 3 4 |
$this->assertEquals($this->isEnabled, $curlHelper->isEnabled()); $this->assertNotContains('No file URL set', $curlHelper->getFileUrl()); $this->assertEquals($this->statusCode, $statusCode); $this->assertEquals($this->isJson, $isJson); |
Here is how my module looks like. I basically created a new helper Helper\Curl.php and a unit test file Test\Unit\Helper\CurlTest.php.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
. ├── Helper │ └── Curl.php ├── README.md ├── Test │ └── Unit │ └── Helper │ └── CurlTest.php ├── composer.json ├── etc │ ├── adminhtml │ │ └── system.xml │ ├── config.xml │ └── module.xml └── registration.php 6 directories, 8 files |
I’ve added some basic methods to the helper, such as isEnabled(), getFileUrl(), getStatusCode(), isJson() and downloadFile(). Nothing fancy, just a helper that can download a file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
<?php /** * Magenizr UnitTest * * @category Magenizr * @package Magenizr_UnitTest * @copyright Copyright (c) 2020 Magenizr (https://www.magenizr.com) */ namespace Magenizr\UnitTest\Helper; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Helper\AbstractHelper; use Magento\Framework\App\Helper\Context; use Magento\Store\Model\ScopeInterface; use Exception; /** * Class Filesystem * * @package Magenizr\UnitTest\Helper */ class Curl extends AbstractHelper { const XML_PATH_GENERAL_ENABLED = 'magenizr_unittest/general/enabled'; const XML_PATH_FILE_URL = 'magenizr_unittest/file/url'; protected $json; protected $statusCode; protected $fileUrl; /** * Config constructor. * * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig */ public function __construct( Context $context, ScopeConfigInterface $scopeConfig ) { parent::__construct($context); $this->scopeConfig = $scopeConfig; } /** * Check if module is enabled * * @return bool */ public function isEnabled() { return $this->scopeConfig->isSetFlag(self::XML_PATH_GENERAL_ENABLED, ScopeInterface::SCOPE_STORE); } /** * Return file url. * * @return mixed */ public function getFileUrl() { $this->fileUrl = $this->scopeConfig->getValue(self::XML_PATH_FILE_URL, ScopeInterface::SCOPE_STORE); if (empty($this->fileUrl)) { throw new Exception(__('No file URL set in System > Configuration.')); } return $this->scopeConfig->getValue(self::XML_PATH_FILE_URL, ScopeInterface::SCOPE_STORE); } /** * Return HTTP status code. * * @return mixed */ public function getStatusCode() { return $this->statusCode; } /** * Set HTTP status code. * * @param $statusCode * @return mixed */ public function setStatusCode($statusCode) { return $this->statusCode = $statusCode; } /** * Check if a string is a valid json string. * * @return bool */ public function isJson() { json_decode($this->json); return (json_last_error() == JSON_ERROR_NONE); } /** * Set JSON string. * * @return bool */ public function setJson($json) { $this->json = $json; } /** * Download a specific file. * * @throws \Exception */ public function downloadFile() { if (empty($this->isEnabled())) { throw new Exception(__('The module is not enabled in Stores > Configuration.')); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->getFileUrl()); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); if (curl_errno($ch)) { throw new Exception(curl_error($ch)); } $output = curl_exec($ch); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $this->setJson($output); $this->setStatusCode($statusCode); curl_close($ch); return $this; } } |
Usually if you want to use system variables from core_config_data you have to inject the ScopeConfigInterface in your constructor. In unit tests it is quite similar. First, you have to create a mockup of the ScopeConfigInterface.
1 |
$this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)->getMockForAbstractClass(); |
Next, you need a mockup of the Context, but this is not always the case.
1 2 |
// Context Mock $this->contextMock = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); |
Now you can inject these two mockups to the helper by using setConstructorArgs like in the below example.
1 2 3 4 5 |
// Curl Helper Mock $this->curlHelperMock = $this->getMockBuilder(CurlHelper::class)->setConstructorArgs([ 'context' => $this->contextMock, 'scopeConfig' => $this->scopeConfigMock, ])->getMock(); |
Now you can define values for the scope configuration. This allows you to enable or disable your module or set random values and see how your code behaves. In the below example, I simply enable the module ( $isEnabled ) and set the file url ( $fileUrl ) to https://raw.githubusercontent.com/ljharb/json-file-plus/master/package.json which I will test with assertEquals and assertNotContains.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<?php /** * Magenizr UnitTest * * @category Magenizr * @package Magenizr_UnitTest * @copyright Copyright (c) 2020 Magenizr (https://www.magenizr.com) */ namespace Magenizr\UnitTest\Test\Unit\Helper; use Magenizr\UnitTest\Helper\Curl as CurlHelper; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Helper\Context; use PHPUnit\Framework\TestCase; class CurlTest extends TestCase { protected $isEnabled = true; protected $statusCode = 200; protected $isJson = true; protected $fileUrl = 'https://raw.githubusercontent.com/ljharb/json-file-plus/master/package.json'; protected $curlHelperMock; protected $contextMock; protected $scopeConfigMock; public function setUp() { // Core Config Data $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)->getMockForAbstractClass(); $this->scopeConfigMock->expects($this->any())->method('isSetFlag')->with(CurlHelper::XML_PATH_GENERAL_ENABLED)->willReturn($this->isEnabled); $this->scopeConfigMock->expects($this->any())->method('getValue')->with(CurlHelper::XML_PATH_FILE_URL)->willReturn($this->fileUrl); // Context Mock $this->contextMock = $this->getMockBuilder(Context::class)->disableOriginalConstructor()->getMock(); // Curl Helper Mock $this->curlHelperMock = $this->getMockBuilder(CurlHelper::class)->setConstructorArgs([ 'context' => $this->contextMock, 'scopeConfig' => $this->scopeConfigMock, ])->getMock(); } /** * Test if enabled flag is defined in the table core_config_data. */ public function testCurlDownloadIfModuleIsEnabled() { // Pass content and scopeConfig to CurlHelper $curlHelper = new CurlHelper($this->contextMock, $this->scopeConfigMock); $file = $curlHelper->downloadFile(); $statusCode = $file->getStatusCode(); $isJson = $file->isJson(); $this->assertEquals($this->isEnabled, $curlHelper->isEnabled()); $this->assertNotContains('No file URL set', $curlHelper->getFileUrl()); $this->assertEquals($this->statusCode, $statusCode); $this->assertEquals($this->isJson, $isJson); } } |
I also define an expected value for the CURL httpStatus and an expected return value of isJson(). Okay, lets run the test and see what happens.
1 2 3 4 5 6 7 8 |
bash-4.4# php vendor/phpunit/phpunit/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/Magenizr/UnitTest/Test PHPUnit 6.5.14 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 1.13 seconds, Memory: 8.00MB OK (1 test, 4 assertions) |
All assertions within the test have succeeded. Let’s see what happens when I change the URL slightly. It should return a 404.
1 |
protected $fileUrl = 'https://raw.githubusercontent.com/ljharb/master/package.json'; |
Yes, the test fails because I’ve received a status code 404 instead of 200.
1 |
$this->assertEquals($this->statusCode, $statusCode); |
1 2 3 4 5 6 7 8 9 |
There was 1 failure: 1) Magenizr\UnitTest\Test\Unit\Helper\CurlTest::testCurlDownloadIfModuleIsEnabled Failed asserting that 404 matches expected 200. /var/www/src/app/code/Magenizr/UnitTest/Test/Unit/Helper/CurlTest.php:60 FAILURES! Tests: 1, Assertions: 3, Failures: 1. |
That’s it. If you have questions or need help, please let me know in the comments.