PHP Profi

Mockery: разные возвращаемые значения при разных аргументах Перевод

php unit test mocks mockery

Иногда при работе с mock объектами библиотеки Mockery, мы хотим сообщить фейковому методу, чтобы он возвращал разные значения для разных аргументов. Это редкий случай, когда мне нужна эта функциональность, но каждый раз, когда мне это нужно, я счастлив, что она есть.

Функция, которая позволяет возвращать различные значения, основываясь на аргументах - это метод Mockery andReturnUsing, который принимает замыкание в качестве аргумента:

// example.php

$dependencyMock = \Mockery::mock('SomeDependency');
$dependencyMock->shouldReceive('callDependency')
    ->andReturnUsing(function ($argument) {
        if ($argument <= 10) {
            return 'low';
        }

        return 'high';
    });

$dependencyMock->callDependency(10); // 'low'
$dependencyMock->callDependency(10); // 'low'
$dependencyMock->callDependency(11); // 'high'

Теперь когда мы вызываем на метод callDependency с параметром 10 или меньше, он будет возвращать 'low', в противном случае он будет возвращать 'high'

Не очень хороший пример, так что давайте взглянем на другой, который чуть ближе к реальной ситуации.

Предположим, мы используем Doctrine`овский entity-менеджер для получения репозиториев наших сущностей в таком классе-сервисе:

// src/ArticleService.php

class ArticleService
{
    public function __construct(EntityManager $em)
    {
        $this->articleRepo = $em->getRepository(Entity\Article::class);
        $this->authorRepo = $em->getRepository(Entity\Author::class);
    }
}

Метод getRepository диспетчера сущностей ( EntityManager`а :) ) вызывается дважды: один раз - для статьи, второй - для автора.

При тестировании мы можем настроить наши моки вот так:

// tests/ArticleServiceTest.php

class ArticleServiceTest extends MockeryTestCase
{
    public function setup()
    {
        $this->authorRepositoryMock = \Mockery::mock(AuthorRepository::class);
        $this->articleRepositoryMock = \Mockery::mock(ArticleRepository::class);
        $this->entityManagerMock = \Mockery::mock(EntityManager::class);
    }

    public function testArticleService()
    {
        $repositoryMap = [
            'Entity\Author' => $this->authorRepositoryMock,
            'Entity\Article' => $this->articleRepositoryMock,
        ];
        $this->entityManagerMock->shouldReceive('getRepository')
            ->andReturnUsing(function($argument) use ($repositoryMap) {
                return $repositoryMap[$argument];
            });

        $articleService = new ArticleService($this->entityManagerMock);
    }
}

Здесь в методе setup мы создаём три mock-объекта, которые нам нужны, а затем в тестовом методе создаём $repositoryMap для сопоставления объектов и репозиториев. Карту сопоставления репозиториев также можно было создать внутри замыкания, переданного в andReturnUsing.

Теперь, когда мы инстанциируем ArticleService с за`mock`аным entity-менеджером, этот mock получит два вызова метода getRepository при вызове конструктора ArticleService. При этом для возвращения соответствующего репозитория мок-объектов, он будет использовать замыкание переданное в andReturnUsing.

Более чем один способ сделать это

Конечно есть и другой способ, чтобы достичь того же. Например, с помощью andReturn, но в этом случае немного больше писанины:

// tests/ArticleServiceTest.php

    public function testArticleService()
    {
        $this->entityManagerMock->shouldReceive('getRepository')
            ->with('Entity\Author')
            ->andReturn($this->authorRepositoryMock);
        $this->entityManagerMock->shouldReceive('getRepository')
            ->with('Entity\Article')
            ->andReturn($this->articleRepositoryMock);

        $articleService = new ArticleService($this->entityManagerMock);
    }

Будет сделано то же самое, что и в предыдущем случае. Можно даже утверждать, что этот второй вариант даже понятнее, чем первый, конечно, для относительно небольшого аргумента “map”. Но в  случае, где будет более двух возможных аргументов, нам поможет andReturnUsing .

П. С.: правильныее это на самом деле было бы отрефакторить код таким образом, что ArticleService  не получал два репозитория из entity manager`а, а инжектить непосредственно их самих.

2017-12-21 alek13 оригинал

Последние посты

Комментарии

авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий