mercredi 31 août 2016

Integration tests mocking facades vs injecting mocks

We have some legacy laravel projects which use facades in the classes.

use Cache;

LegacyClass
{
    public function cacheFunctionOne()
    {
         $result = Cache::someFunction('parameter');

         // logic to manipulate result

         return $result;
    }

    public function cacheFunctionTwo()
    {
         $result = Cache::someFunction('parameter');

         // different logic to manipulate result

         return $result;
    }
}

Our more recent projects use dependency injection of the underlying laravel classes that the facades represent as has been hinted at by Taylor Otwell himself. (We use constructor injection for each class, but to keep the example short, here I use method injection and use a single class.)

use Illuminate\Cache\Repository as Cache;

ModernClass
{
    public function cacheFunctionOne(Cache $cache)
    {
         $result = $cache->someFunction('parameter');

         // logic to manipulate result

         return $result;
    }

    public function cacheFunctionTwo(Cache $cache)
    {
         $result = $cache->someFunction('parameter');

         // different logic to manipulate result

         return $result;
    }
}

I know facades can be mocked

public function testExample()
{
    Cache::shouldReceive('get')
                ->once()
                ->with('key')
                ->andReturn('value');

    $this->visit('/users')->see('value');
}

Which works nicely for unit tests. The problem I am trying to understand is if these facades are mocked 'globally'.

For example, lets imagine I am writing an integration test (testing a few interconnected classes while mocking services - not an end to end test using live services) which at some point, executes two separate classes which contain the same facade that calls the same method with the same parameters.

In between these classes being called, is some complex functionality that changes what data is returned by that facades method using the same parameter.*

$modernClass->cacheFunctionOne($cache); // easily mocked

// logic that changes data returned by laravel Cache object function 'someFunction'

$modernClass->cacheFunctionTwo($cache); // easily mocked with a different mock

Our modern classes are easy to test because the underlying class that the facade represents is injected into each class (in this example, each method). This means I can create two separate mocks and inject them into each class (method) to mock the different results.

$legacyClass->cacheFunctionOne();

// logic that changes data returned by laravel Cache object function 'someFunction'

$legacyClass->cacheFunctionTwo();

In the legacy systems though, it would seem that the mocked facade is 'global' so that when the facade is run in each class, the exact same value is returned.

Am I correct in thinking this?

*I understand this example may seem completely redundant from a code architecture and testing point of view, but I am stripping out all real functionality to try and give some sort of 'simple' example of what I am asking.

Aucun commentaire:

Enregistrer un commentaire