Magento 2.3.x – Unit Tests with ScopeConfigInterface

Magento2 Teaser

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.

  1. Is the module enabled in Stores > Configuration > General?
  2. Is a URL defined?
  3. Does the URL return a correct HTTP response?
  4. Is the downloaded content a valid JSON string?

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.

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.

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.

Next, you need a mockup of the Context, but this is not always the case.

Now you can inject these two mockups to the helper by using setConstructorArgs like in the below example.

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.

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.

All assertions within the test have succeeded. Let’s see what happens when I change the URL slightly. It should return a 404.

Yes, the test fails because I’ve received a status code 404 instead of 200.

That’s it. If you have questions or need help, please let me know in the comments.

Notice: Use of undefined constant T_CURLY_OPEN – assumed ‘T_CURLY_OPEN’ in /var/www/src/setup/src/ Magento/Setup/Module/ Di/Code/Reader/FileClassScanner.php on line 72

Today I finally had some time to cleaned up my Dockerfile for Magento 2. I am using Alpine Linux for most of my Magento 2 projects which usually includes NGINX, PHP-FPM and MariaDB.

While testing my updated Dockerfile I came across the following PHP notice which prevented the deployment scripts from finishing the Magento 2 setup.

The problem was simply a missing PHP extension php7.1-tokenizer which has resolved the issue after re-creating the container with –build.

 

Font Awesome Icons without CDN in Magento2

How to ... in Magento

I recently worked on a custom module where one of the requirements was to use Font Awesome for custom forms and notifications / alerts.

Most of the people in the Magento community suggest to load Font Awesome via CDN, but this can cause ” blank squares ” and eventually slow down the front-end on slower connections.

So instead using a CDN I decided to add those libraries in Magento which will be merged with the static view files. Here is how it works.

1. Copy files

Go to Host Font Awesome Yourself and download the entire library. The zip file should contain css, js and less files as well as webfonts.

Fontawesome Less Files

Copy all less files to the following folder.

Next, copy all files from the webfonts folder to the below path.

Fontawesome Font Files

Make sure you only copy the less files ( no CSS files ).

2. Update Layout XML

Don’t load the library globally if you only need it at one place. In my case, I added those library to my custom controller only. For example:

3. Template

Once you have re-compiled your code, you should be able to use Front Awesome in your template. For example:

Font Awesome Example

Btw. the same works for the adminhtml area in ./view/adminhtml/.

Working with files and folders in Magento2

Working with files and folders in Magento2

Magento® 2 offers you a bunch methods for file and folder operations, which allow you to implement quality modules in a short time. Especially if you are planning to publish modules on the Magento Marketplace you have to use those functions, otherwise you won’t meet Magento’s coding standards. In order to have access to cool functions like mkdirRecursive, deleteDirectory or changePermissionsRecursively simply inject \Magento\Framework\Filesystem\Driver\File in your constructor.

Once you have re-compiled your code, you should be able to use those functions in your modules like below.

More functions are listed below.

1. Check if the file or folder exists on the file system.

2. Returns information like ctime and mtime or sizeatimeuid and gid.

3. Check if a file is readable

4. Check if path is a file ( not folder )

5. Check if path is a directory ( not a file )

6. Get content of a regular file

7. Check if a regular file is writable ( not a folder )

8. Return the parent directory of a path

9. Create a new directory with specific permissions

10. Create a new directory recursively

11. Reads a directory and returns an array with all paths of its sub-directories

12. Search for a path

13. Rename a regular file or a directory

14. Copy a file from A to B

15. Create a symlink

16. Delete a regular file ( not a file )

17. Delete a directory recursively

18. Change permissions of a directory

19. Change permissions of a directory and file recursively

20. Manipulate the access and modification time of a file

21. Write content to a file

22. Opens a file like fopen

23. Returns a line from the given handle

24. Reads a file like fread.

25. Reads a CSV file like fgetcsv

26. Returns position of a pointer like ftell

27. Seeks to a specific offset like fseek

28. Returns true if pointer is at the end of a file

29. Close a file

30. Write to a file like fwrite

31. Write one row to a CSV file

32. Flush output of a file. See fflush.

33. Lock a file before write content to a file

34. Unlock a file after all file manipulations are done

35. Return a absolute path

36. Fixes a path separator

37. Reads a directory recursively and returns paths as an array

38. Return the real path like realpath

39. Returns a path for link

Cheers

The use of function is_readable() is discouraged in Magento2

How to fix ... in Magento

Today I was working on method which is responsible to read information of an uploaded file. I came across the same problem I had in my previous blog post ( The use of function filectime() / filemtime() is discouraged in Magento2 ).

This time it was able to get rid of the warning quickly, because \Magento\Framework\Filesystem\Driver\File was already injected in my constructor.

I simply replaced is_readable with the below condition.

That’s it. Check my blog post about Working with files and folders in Magento2 for more information.

The use of function filectime() / filemtime() is discouraged in Magento2

How to fix ... in Magento

The problem I had today was located in a helper ( [Namespace]\[Module]\Helper\FileSystem ) which I have created for some basic filesystem operations. The below message was the result of Magento’s EQP which I use before I commit any code.

The warning message basically means, I should find another way to get the creation and modification time of a file because of Magento’s coding standards. I have done that a couple of times already and would like to share my solution this time. First you have to pass \Magento\Framework\Filesystem\Driver\File in your existing constructor or create a new one.

Once you have re-compiled your code, you have access to the file stats as follows.

Beside ctime and mtime the array $fileData should have other file information like size, atime, uid and gid.

UI Components and searchResultToOutput in Magento2

Today I had to fix the following issue in a custom module. The error appeared on the UI Component list view.

I found the reason of the problem in the di.xml file. All what was missing was the following virtualType node. After adding this and re-compiling the code, the list view was fixed.

UI Component Listing And Not registered handle in Magento2

Today I created a new listing for a custom module, but unfortunately all what I got was the error message below.

After digging around for few minutes I noticed the above handler was actually missing in the di.xml file. I simply added the following collections argument and re-compiled the code.

All good!

The requested component (“settings”) is not found. Before using, you must add the implementation.

I tried to implement a simple field validation for a checkbox Apply Special Price which I needed on the Catalog > Product > Edit page. Unfortunately my first attempt below didn’t work and broke the whole grid table.

Here is how I have defined the field apply_special_price in category_form.xml based on different stackoverflow posts.

As I said, it didn’t work at all. So, I kept researching and found a solution which allowed me to implement the validation rule without the nodes <settings> and <formElements> inside of <field>.

Basically, everything has to be defined inside of config otherwise the XML for your custom UI component will be invalid. The section validation allows you to define filters for pretty much every use case. The example below sets a field as mandatory and detects white spaces. Beside that it will make sure the value matches a specific regex pattern.

More validation options are available out-of-the-box.

Update composer.lock without updating code

Recently I had to fix a broken composer.lock but without actually updating code. I have found the composer option ” nothing ” which updates the composer.lock file only.

Once the command ran successfully, you should see a line ” Writing lock file ” in the output.

If you have issues with dependencies you can update the composer.lock file with –ignore-platform-reqs.