Как написать тест PHPUnit для команды Laravel Artisan, которая вызывает внешний API без физического вызова этого API? - PullRequest
0 голосов
/ 03 июля 2018

Краткое введение, чтобы дать вам представление о том, чего я хочу достичь ...

У меня есть команда Laravel 5.6 artisan, которая вызывает внешний API с простым GET-запросом для получения некоторых данных. Я хочу проверить это без физического вызова API . (Я хочу быть в автономном режиме и все еще иметь тестовый зеленый тест).

Теперь краткое объяснение того, как устроена логика.

1) Эта команда ремесленника (php artisan export:data) имеет свой собственный конструктор (__construct), куда я добавляю кучу вещей. Одна из вещей, которые я ввожу, это класс ExportApiService. Легко.

public function __construct(
    ExportApiService $exportApiService,
) {
    parent::__construct();

    $this->exportApiService = $exportApiService;
}

2) Теперь ExportApiService расширяется abstract class AbstractApiService. Внутри этого абстрактного класса я создал конструктор, в котором я просто внедряю GuzzleHttp\Client в свойство $client, чтобы в ExportApiService я мог вызывать API с помощью $this->client. Легко.

3) Так что у моего ExportApiService есть метод с именем exportData. Trivial. Работает.

public function exportData(array $args = []): Collection
{
   $response = $this->client->request('GET', 'https://blahblah.blah');

   return collect(json_decode($response->getBody()->getContents(), true));
}

4) Итак, наконец, в моей команде ремесленников (которую я ввел в пункте № 1) я вызываю:

public function handle()
{
    $exportedData = $this->exportApiService->exportData($this->args);

    $this->info('Exported ' . count($exportedData) . ' records');
}

Я хочу проверить, соответствует ли вывод моим ожиданиям , не вызывая этот внешний API вообще . Я хочу как-то поиздеваться ...

public function testExportSuccessful()
{
    $this->console->call('export:data', [$some_arguments]);

    $output = $this->console->output();

    $this->assertContains('Exported 123 records', $output); // this does not work :(
}

Теперь мой точный вопрос - как я могу заставить Laravel / PHPUnit возвращать фиксированное значение каждый раз, когда команда ремесленника будет вызывать exportData()?

Что вы пробовали?

Я попытался создать метод setUp() со следующим кодом:

public function setUp()
{
    parent::setUp();

    $mock = $this->createMock(ExportApiService::class);

    $mock->method('exportData')->willReturn(123); // give me a dummy integer each time exportData is being called so that I can use is in the assert at the end
}

Но это совсем не работает, хотя для меня это имеет смысл.

Спасибо за любую помощь.

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

Вы создали макет, но этот макет никогда не достигает вашего кода. Я просто лежу вокруг. Чтобы убедиться, что он достигает вашего кода, вы должны понимать, что происходит, когда вы помещаете класс в конструктор.

Когда вы помещаете класс в свою конструкцию, Laravel запрашивает у контейнера ioc экземпляр запрошенного класса. Если он недоступен, он попытается создать его для вас. Вот где вы можете получить свой макет.

По сути, все, что вам нужно сделать, это сообщить Laravel, какой экземпляр следует использовать, когда нужен конкретный класс. Все, что вам нужно сделать, это добавить эту строку после того, как вы создали макет:

app()->instance(ExportApiService::class, $mock);

Таким образом, вы говорите Laravel, что каждый раз, когда вам нужен ExportApiService, Laravel должен вместо этого возвращать $mock.

0 голосов
/ 03 июля 2018

Вы создали поддельный объект, но не связали его в контейнер. Без привязки, когда Laravel запустит вашу команду, она просто сгенерирует новый ExportApiService экземпляр.

public function setUp()
{
    parent::setUp();

    $mock = $this->createMock(ExportApiService::class);

    $mock->method('exportData')->willReturn(123);

    // bind the mock in the container, so whenever you ask for
    // a new ExportApiService, you'll get your mocked object.
    $this->app->instance(ExportApiService::class, $mock);
}

Другой вариант, вместо того, чтобы делать это, - это высмеивать ваши запросы Guzzle. Таким образом, весь ваш код выполняется как обычно, но когда вы делаете свой вызов API, Guzzle возвращает предопределенный ответ вместо фактического выполнения вызова. Вы можете найти документы по Guzzle на тестировании здесь .

После того, как вы сконфигурируете свой клиент с поддельными ответами, вы должны будете связать этот экземпляр клиента с контейнером, чтобы при создании ExportApiService Laravel вставлял клиента, которого вы настроили, с поддельными ответами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...