You write unit tests for a Magento 2 module and need to mock a core class, like the store model. This class has a few dependencies which have more dependencies and it’s getting complicated to mock. Although PHPUnit 5.4 has a createMock() method that automatically stubs methods and let them return new stubs if they have a return type.

Or: you mock a service contract like getList() in the product repository, which takes a search criteria instance as parameter, which must be returned by a search criteria factory. Then it returns a product search result, which has a getItems() method, which returns a array of products. So you end up mocking countless dependendencies for a single method call

Do these situations sound familiar? It does not need to be like that, though! Let me show you a sane way to deal with Magento dependencies.

In integration tests with EcomDev_PHPUnit_Test_Case_Controller there is a useful helper method adminSession(), to test requests in the Magento backend. With Magento Enterprise you might get this error message if you use it:

Exception: Warning: in_array() expects parameter 2 to be array, null given in /home/vagrant/mwdental/www/app/code/core/Enterprise/AdminGws/Model/Role.php on line 83

As seen on Magento EE 1.14.1.

Without disclosing details about the Enterprise modules, here a solution where the responsible observer gets mocked:

It makes me proud that the great Matthew Weier O’Phinney 1 now describes the same technique to test code that emits output, especially code that sends headers with the built-in header() function. Read more in his post here:

In my opinion this is a great example of how useful this method is. “Headers already sent” errors in your unit tests can drive you crazy. Unfortunately, there are still many applications that do not use namespaces (*cough* Magento *cough*) where it does not work.

Who doesn’t know this situation: When testing features like the guest checkout manually, you have to fill all form fields tedously again and again. With Chrome auto complete or “test”, Ctrl + C, Ctrl + V it’s halfway fast but still a bit annoying. And what if the test data is supposed to make sense and should not be the same every time?

Inspired by this article on css-tricks.com I developed a little Magento module, that enables filling Magento forms with dummy data with one mouse click. Currently it is implemented for billing address and shipping address.

Configuration

Of course you want to see this button only on your development system, that’s why there are two ways to hide it: Either disable the module completely per configuration or show the button only in developer mode:

The nice thing about the second variation is that the JavaScript still gets loaded, so that you can substitute the button with a bookmarklet. Here the snippets:

Amongst the features, that are hard to test with Unit Tests, is generating PDF documents.

The command line tool PDFBox with the option ExtractText comes in handy:

This application will extract all text from the given PDF document.

This allows us, to test the textual content of the document or searching for specific strings inside.

It gets interesting with the option -html, which converts the PDF to HTML instead. Thus structure and formatting gets at least remotely testable.

Unfortunately the tool does not work with streams, we have to use temporary files. A simple example for a function that receives a PDF document as string, converts it to HTML with PdfBox and returns the HTML string:

For regression tests or refactoring it sometimes is enough to test that the generated PDF did not change in comparision to a reference PDF. This could be achieved with a hash value but a PDF itself is not binary equal every time, probably due to timestamps. But a hash of the converted HTML is sufficient:

The PHP function exec() for executing system commands must not be disabled. On shared hosts this is usually the case for security reasons; for local execution of Unit Tests it shouldn’t be a problem to allow exec().PHP-CLI, i.e. PHP on the command line usually uses a different php.ini configuration file than PHP-CGI for the web. The command php --ini shows, which INI files are loaded in CLI mode. If necessary, edit these to remove exec from the disable_functions list.

A PSR-0 compatible autoloader, as shipped with most frameworks. Otherwise you will need to include the single PHP files.

Usage

First you’ll have to specify the full path to the PdfBox JAR. Afterwards you can call the conversion methods, for example:

The second parameter is either the PDF as binary string ($content) or the file name of a PDF ($fileName). The second parameter, if provided, is a file name for the output. In this file the text, or HTML, will be saved.

A common problem in Unit Testing in PHP is testing something that depends on the current time. For a determined test it should be possible to set the time in your test script without really changing the system settings. In this article I’ll describe how it is usually done with OOP and then come to an alternative solution with much less code that makes use of the new features in PHP 5.3.

Then you mock the Calendar class in your tests and pass it to the test subject. I won’t go into further details because you probaly know the concept of mocking and how to do this in your favourite unit testing framework. After all this article is not about mocking classes, because I have:

A simpler solution with namespaces

If you are using PHP 5.3 namespaces you are lucky because you won’t need all this overhead and probably no changes in your classes at all. The trick is to override built-in functions in your current namespace. Consider this namespaced version of the class from above:

As you can see, no overhead, just a straightforward call to date() and time(). To test this with specific times we implement a test case as follows (Example in PHPUnit but works as well with other frameworks):

The crucial point here is that we implement a new function named exaclty like a built-in function. You cannot replace functions but since this is defined in the namespace \My\Namespace it does not replace anything. In fact it is a new function with the fully qualified name \My\Namespace\time()

The test subject now calls time() as unqualified name so PHP looks for the function in the current namespace at first. That is \My\Namespace\time() in our example. I recommend the section about name resolution rules in the manual for further reading.Important Implication: It does not work if you use the global functions with fully qualified names (i.E. \time()) in your test subjects!

You can implement this function however you like, I decided to make the return value configurable within the test case via a static property that gets resetted after each test and if it is not set the real time is used.

I hope this solution helps, it may feel hackish but for me it made testing a lot easier!