Mocking with Anonymous Classes

7th May '18

Disclaimer - This article does not contain anything new, it just documents an approach of using anonymous classes in testing that I found particularly useful.

 

Anonymous classes

PHP7 gave us some cool features, including anonymous classes. These are classes that you can define on the fly, associate with a variable and instantiate whenever you like.

In a well built application you might think there are limited use cases for these, with all classes you need having their own file and specific place in the application, but what about classes that are incredibly custom, few lines long and barely used?

Yeah you can build a flexible class with nice setters/getters, but you could also just define the exact properties you want at the time you want them.

Example time, let’s say you have an application that contains Earth’s mightiest heroes. The details of each Hero are stored in a class.

If you have a class called Thor, part of it might look like this:

<?php
class Thor implements Avenger {
    use Lightening;
    protected $weapon;

    public function getWeapon() {
        return $this->weapon;
    }

   public function setWeapon(Weapon $weapon = null) {
       $this->weapon = $weapon;

        return $this;
    }
}

Quite a simple class leveraging a few nice OO concepts which includes chaining (for chain lightning!).

Now originally Thor starts with his hammer, Mjolnir, then in the films he met his sister and loses it. Later on, he fights with whatever he can get his hands on.

At this point, when the weapon he uses is not a permanent fixture, who really has time to create a new class for each change. If I expressed this sequence of events in code it might look like this:

$thor = new Thor();
$thor->setWeapon(new Mjolnir());

// Oh no Hela comes along.
$thor->setWeapon(null);

// Hmm these swords seem temporary
$thor->setWeapon(new class extends Weapon { protected $name = 'swords'; });

Obviously this is a stretched example, but it highlights how easy it is to throw around an anonymous class in place of a real class.

Anonymous classes can do everything regularly classes can do.

 

Mocking Objects

A week ago I was writing a complex test case in a Laravel application for a function that made two calls to a class in the service container. The first call retrieves a complex string, and the second parsed it for a certain value. Since I was passing dummy data into the method, both these calls would fail normally. I had to do some mocking!

The simplest solution was mocking both methods to return true, but since the main point of the function is the second call, the test itself would become a bit pointless. So I needed to mock the first call whilst keeping the complex logic of the second call. After a bit of time considering my options, I eventually realised an anonymous class extending the old class was the solution.

 

Extending Bindings in Laravel’s Service Container

The method I was testing was using a class in Laravel’s Service Container, so the first tricky bit was modifying what the container resolves to. This is made super simple using the extend method:

https://laravel.com/docs/5.6/container#extending-bindings

$this->app->extend(Service::class, function($service) {
    return new DecoratedService($service);
});

 

Mocking with Anonymous classes

Now we know how to modify the Service Container, we override the old service with our new class:

$this->app->extend(Service::class, function () {
    return new class extends Service {
        public function getData($key) {
            return ‘really complex string that needs parsing for random rubbish’;
        }
    };
});

And there we have it. When we call the function we are testing, it will go to the service container and retrieve this new class. It will call getData with a dummy key and receive our complex string back to be parsed as expected.

The above code can be placed anywhere, say in the Test constructor, in the setUp method or in the specific test case.

 

Conclusion

Though mocking libraries can be powerful, sometimes it might be easier or clearer to mimic class behaviour by extending it. The above shows a simple example of this concept, but there’s plenty more you can do. For one this technique can give you access to protected methods for testing without using reflection.

Happy testing!

Sign Up For My Newsletter

Every week I put together an article digest of web related articles I found interesting and wanted to share, if you would like to receive this email please subscribe below!

* indicates required
comments powered by Disqus