Law of Demeter in Testing Web Applications

Writing a testable code of an application is as important as writing tests itself. While testing, besides S.O.L.I.D. (single responsibility, open-closed, Liskov substitution, interface segregation and dependency inversion - the five basic principles of object-oriented programming and design), compliance with the Law of Demeter may come in handy as well.

When a developer doesn’t adhere to the guideline of LoD, mocking variables which the code uses to perform, its logic becomes problematic.

Let’s take as an example this simple class:

<?php
class EventListener
{
    public function onEvent(ProductEvent $event)
    {
        if ($event->getProduct()->inHiddenCategory()) {
            throw new \DomainException('This category is not accessible');
        }
    }
}

Testing the behavior of this class (by throwing an exception if the product category is hidden) requires us to pass the test event’s class containing a product’s mock. The mock would have to contain a category's mock, and the category’s mock would then have to contain state’s mock. The latest would perform isHidden() method and would return values that interest us.

When we have no influence on the code written this way of writing the test is necessary. However, if we are responsible for the code, it would be better to simplify everything by adding the method for the product. Then, for example, the code would like this:

<?php

class EventListener
{
    public function onEvent(ProductEvent $event)
    {
        if ($event->getProduct()->getCategory()->getState()->isHidden()) {
            throw new \DomainException('This category is not accessible');
        }
    }
}

And the test itself could look like this:

<?php

class EventListenerTest extends \PHPUnit_Framework_TestCase
{
    public function testItThrowsExceptionIfProductInHiddenCategory()
    {
        $product = $this->prophesize(Product::class);
        $product->inHiddenCategory()->willReturn(true);

        $this->expectException(\DomainException::class);

        $listener = new EventListener();
        $listener->onEvent(new ProductEvent($product));
    }

    public function testItDoesNothingIfProductNotInHiddenCategory()
    {
        $product = $this->prophesize(Product::class);
        $product->inHiddenCategory()->willReturn(false);

        $listener = new EventListener();
        $listener->onEvent(new ProductEvent($product));
    }
}

How many time have we seen such constructor?

<?php

public function __construct(EntityManager $em)
{
    $this->repository = $em->getRepository(Product::class);
}>

Again, mocking in the test will be impeded. We could have just done the following:

<?php

public function __construct(EntityManager $em)
{
    $this->repository = $em->getRepository(Product::class);
}>

* * *
If this was helpful, visit Mike's blog.

scale

Ready to make your SaaS Scalable?

Fix most important issues within days from the kick-off

ESTIMATE PROJECTOr contact us directly at: [email protected]

Related posts

  • Project Release Checklist

    Project Release Checklist

    At Accesto we recently came up with a project release checklist that helps us to make sure that we did not miss anything. It helped us a lot…

    Read more
  • Page Object Pattern

    Page Object Pattern

    As important and critical unit tests are to our applications, functional testing of the front part should be a must part of any project too…

    Read more