Testing Date And Time with Clock Objects

Did you ever write unit tests for code that dealt with date and time? Since time itself is out of your control, you might have used one of these approaches:

  • Test doubles, e.g. for the DateTime class in PHP. To make this work, DateTime or a factory thereof must be passed to the subject under test via dependency injection. My most popular blog post of all time is still “How to mock time() in PHPUnit” about mocking core functions like date() and time() if necessary (a hacky workaround, mind you)
  • Use derived values of the current time in fixtures and assertions, probably with some margin, e.g.
    assertEquals(\strtotime(“+1 day”), $cache->expiresAt(), “1”)
    where $cache->expiresAt() is the value we test and “1” is the allowed margin.

The latter is not very stable; it is likely that you run into edge cases where the tests are not deterministic. The former does not have this problem, but can result in a complicated setup of the test double.

Luckily, there is a third way, namely using a custom Clock (or Calendar) object that provides the current time and can easily be replaced with an (also custom) test double.

Continue reading on integer-net.com

MageTestFest – A Unique Conference and One Time Opportunity

If you are interested in Software Testing and/or Magento development, the most interesting event of the year is approaching: MageTestFest in Amerfoort (NL)!

  • Nov 15: Workshop PHPUnit (Sebastian Bergmann)
  • Nov 16: Workshop DDD (Mathias Verraes)
  • Nov 17: Conference Day (Agenda)
  • Nov 18: Magento Contribution Day (Hackathon)

Continue reading “MageTestFest – A Unique Conference and One Time Opportunity”

What to mock in a Magento 2 unit test

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.

Continue reading “What to mock in a Magento 2 unit test”

Mock Admin Session With EcomDev_PHPUnit In Magento Enterprise

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:

$adminObserverMock = $this->getModelMock(
    'enterprise_admingws/observer',
    array('adminControllerPredispatch'));
$adminObserverMock->expects($this->any())
    ->method('adminControllerPredispatch')
    ->will($this->returnSelf());
$this->replaceByMock('singleton', 'enterprise_admingws/observer',
    $adminObserverMock);
$this->adminSession();

PHP: Mock header() to Unit Test Controllers

In 2011 I suggested a technique to mock functions in PHP Unit Tests that takes advantage of the name resolution rules of PHP namespaces. You can read it here:

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.

Notes:

  1. for those who don’t know him: He’s the Zend Framework Project Lead and you should follow his blog at http://mwop.net/blog.html!

Magento Testing: Fill Forms with one click

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.

Github-Repository: SSE_FormFiller

And this is how it looks:

Screenshot: Forms in Checkout

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:

SSE_FormFiller Configuration

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:

Billing address form

JavaScript:

formFiller.fill(billingForm.form)

Bookmarklet (right click, add as bookmark!)

Shipping address form

JavaScript:

formFiller.fill(shippingForm.form)

Bookmarklet (right click, add as bookmark!)

Technology

A bit of information on the implementation:

  • The dummy data comes from Faker.js.
  • The button gets added to choosen blocks with an observer for core_block_abstract_to_html_after
  • In the JavaScript the form ID determines which fields should be filled.

The module is designed to be extended for other forms as well. Suggestions and of course pull requests on Github are most welcome! Continue reading “Magento Testing: Fill Forms with one click”

Unit Test Generated PDFs with PHPUnit and PDFBox

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:

PDF

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:

/**
 * @var string $streamIn binary string with generated PDF
 * @return string HTML string
 */
function htmlFromPdf($streamIn)
{
  $pdf = tempnam();
  file_put_contents($pdf, $streamIn);
  $txt = tempnam();
  exec('java -jar pdfbox-app-x.y.z.jar ExtractText -encoding UTF-8 -html ' . $pdf . ' ' . $txt);
  $streamOut = file_get_contents($txt);
  unlink($pdf);
  unlink($txt);
  return $streamOut;
}

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:

        // In PHPUnit test case:
        $converter = new PdfBox();
        $html = $converter->htmlFromPdfStream($pdf);
        $this->assertEquals('336edd9ee49b57e6dba5dc04602765056ce05b91', sha1($html), 'Hash of PDF content');

In this example I use a self-written class PdfBox, which encapsulated the call to Apache PdfBox. The code is available under BSD Licence on GitHub: PHP PdfBox

PHP PdfBox

Requirements

  1. Java Runtime Environment, with “java” in the system path. To test this, run java -version on the command line. If you see information about the Java version, everything is fine
  2. Apache PdfBox as executable JAR file. You can download it here: http://pdfbox.apache.org/downloads.html
  3. 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.
  4. 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:

use SGH\PdfBox

//$pdf = GENERATED_PDF;
$converter = new PdfBox;
$converter->setPathToPdfBox('/usr/bin/pdfbox-app-1.7.0.jar');
$text = $converter->textFromPdfStream($pdf);
$html = $converter->htmlFromPdfStream($pdf);
$dom  = $converter->domFromPdfStream($pdf);

The following conversion methods exist:

  • string textFromPdfStream($content, $saveToFile = null)
  • string htmlFromPdfStream($content, $saveToFile = null)
  • DomDocument domFromPdfStream($content, $saveToFile = null)
  • string textFromPdfFile($fileName, $saveToFile = null)
  • string htmlFromPdfFile($fileName, $saveToFile = null)
  • DomDocument domFromPdfFile($fileName, $saveToFile = null)

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 few additional PdfBox-Options can be useful as well:

// Only extract pages 2-5
$converter->getOptions()
    ->setStartPage(2)
    ->setEndPage(5);

// ignore corrupt PDF objects
$converter->getOptions()
    ->setForce(true);

Everything else should be clear from the PhpDoc comments. Happy Testing! Continue reading “Unit Test Generated PDFs with PHPUnit and PDFBox”

PHP: “Mocking” built-in functions like time() in Unit Tests

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.

The usual approach would be a wrapper class like this:

class Calendar
{
    public function time()
    {
        return time();
    }
    public function date($format, $time = null)
    {
        return date($format, $time ?: $this->time());
    }
    // ...
}

Now any class that uses date/time functions has to be modified to use the Calendar class via Dependency Injection:

class SomeClass
{
    /**
     * @var Calendar
     */
    private $calendar;

    public function __construct(Calendar $calendar = null)
    {
        $this->calendar = $calendar ?: new Calendar;
    }
    public function oneHourAgo()
    {
        return $this->calendar->date('H:i:s', $this->calendar->time() - 3600);
    }
}

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:

namespace My\Namespace;

class SomeClass
{
    public function oneHourAgo()
    {
        return date('H:i:s', time() - 3600);
    }
}

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):

namespace My\Namespace;

require_once 'PHPUnit\Framework\TestCase.php';

/**
 * Override time() in current namespace for testing
 *
 * @return int
 */
function time()
{
	return SomeClassTest::$now ?: \time();
}

class SomeClassTest extends \PHPUnit_Framework_TestCase
{
	/**
	 * @var int $now Timestamp that will be returned by time()
	 */
	public static $now;

	/**
	 * @var SomeClass $someClass Test subject
	 */
	private $someClass;

	/**
	 * Create test subject before test
	 */
	protected function setUp()
	{
		parent::setUp();
		$this->someClass = new SomeClass;
	}
	/**
	 * Reset custom time after test
	 */
	protected function tearDown()
	{
		self::$now = null;
	}

	/*
	 * Test cases
	 */
	public function testOneHourAgoFromNoon()
	{
		self::$now = strtotime('12:00');
		$this->assertEquals('11:00', $this->someClass->oneHourAgo());
	}
	public function testOneHourAgoFromMidnight()
	{
		self::$now = strtotime('0:00');
		$this->assertEquals('23:00', $this->someClass->oneHourAgo());
	}
}

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!