Law of Demeter in Testing Web Applications

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.

Michael Zukowski

Michael Zukowski

Programming enthusiast, mostly PHP. Father of two.

Subscribe to our newsletter

You may also like:

This website uses cookies to guarantee the best experience for the user. If you continue browsing, we consider that you agree to their use.