Использование цепных частичных макетов на интерфейсе, используемом в ремесленной команде - PullRequest
0 голосов
/ 21 января 2019

Я пытаюсь протестировать команду ремесленников в Laravel 5.3. Команда вызывает функции в классе, который предоставляется конструктору команд в качестве интерфейса. Этот интерфейс вызывает функции в другом классе. Это общие настройки.

class MyCommand
{
    public function __construct(MyRepositoryInterface $interface)
    {
        ...
        $this->interface = $interface;
        ...
    }

    public function fire()
    {
        $this->interface->useTheSecondClass();
    }
}

class MyRepository implements MyRepositoryInterface
{
    public function __construct(MySecondRepositoryInterface $second)
    {
        ...
        $this->second = $second;
        ...
    }

    public function useTheSecondClass()
    {
        $response = $this->second->getSomeValue();
    }
}

class MySecondRepository implements MySecondRepositoryInterface
{
    /**
     * @return Some\External\Client
     */
    public function getExternalClient()
    {
        ....
        return $external_client;
    }

    public function getSomeValue()
    {
        $client = $this->getExternalClient();

        $something = $client->doSomething();

        Event::fire('some event based on $something`);

        return $something;
    }
}

Я пытаюсь смоделировать переменную, возвращаемую в MySecondRepository -> getExternalClient(), чтобы я мог подделать внешний вызов API и использовать эти поддельные данные для проверки функциональности MySecondRepository -> getSomeValue() и MyRepository -> useTheSecondClass(), вызванной из класса MyCommand как таковой.

public function testMyCommand()
{
    $external_client_mock = Mockery::mock("Some\External\Client");
    $external_client_mock->shouldReceive("doSomething")
        ->andReturn("some values");

    $second_repository_mock = Mockery::mock("MySecondRepositoryInterface")
        ->makePartial();
    $second_repository_mock->shouldReceive("getExternalClient")
        ->andReturn($external_client_mock);

    $resource = new MyRepository($second_repository_mock);
    $this->app->instance("MyRepositoryInterface", $resource);

    $class = App::make(MyCommand::class);
    $class->fire();

    ...
}

Я использовал эту же цепочку для успешного тестирования переменной $resource напрямую (например, тестирование $resource->useTheSecondClass() напрямую, а не через MyCommand), но в этой ситуации, когда $second_repository_mock->getExternalClient() корректно насмехается, Тест все еще ожидает, что будет $second_repository_mock->getSomeValue(). Поскольку $second_repository_mock настроен на частичную имитацию, я не понимаю, почему он все еще ищет все функции, которые нужно смоделировать.

Если я удаляю часть $external_client_mock и полностью имитирую $second_repository_mock, мои тесты напрямую связаны с работой команды ремесленника, однако я хотел бы проверить, что событие, инициированное в getSomeValue(), правильно обрабатывается из команды ремесленника , что я не могу сделать, если я не могу использовать частичный макет.

Кто-нибудь знает, почему это не работает?

1 Ответ

0 голосов
/ 29 января 2019

Вы пытаетесь смоделировать интерфейс, который не имеет смысла. Интерфейсы не имеют конкретных реализаций для использования. Это приводит к неправильному коду. Просто издевайтесь над вашими хранилищами, и это будет работать.
EDIT
Просто запустил тесты со следующим рефакторингом, и они перешли на зеленый:

public function testMyCommand()  // phpcs:ignore
{
    $external_client_mock = \Mockery::mock("App\Client");
    $external_client_mock->shouldReceive("doSomething")
        ->andReturn("some values");

    $second_repository_mock = \Mockery::mock("App\MySecondRepository[getExternalClient]")
        ->shouldIgnoreMissing();
    $second_repository_mock->shouldReceive("getExternalClient")
        ->andReturn($external_client_mock);

    $resource = new MyRepository($second_repository_mock);
    $this->app->instance("App\MyRepositoryInterface", $resource);

    $class = \App::make(\App\MyClass::class);
    $class->fire();
}

Единственное существенное отличие состоит в том, что у вас было App\MyCommand от второй до последней строки, а не App\MyClass.

...