Как смоделировать сервис (или ServiceProvider) при запуске функциональных тестов в Laravel? - PullRequest
0 голосов
/ 10 мая 2018

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

Я пишу HTTP API, помимо прочего, для составления списка, создания и удаления системных пользователей на сервере Linux. Структура выглядит так:

  • Маршруты к /v1/users соединяют GET, POST и DELETE глаголов с методами контроллера get, create и delete соответственно.
  • Контроллер App\Http\Controllers\UserController фактически не выполняет системные вызовы, что выполняется службой App\Services\Users.
  • Служба создается ServiceProvider App\Providers\Server\Users, который регистрирует singleton службы на отсроченной основе.
  • Служба запускается Laravel автоматически и автоматически внедряется в конструктор контроллера.

ОК, так что все это работает. Я также написал некоторый тестовый код, например:

public function testGetUsers()
{
    $response = $this->json('GET', '/v1/users');
    /* @var $response \Illuminate\Http\JsonResponse */

    $response
        ->assertStatus(200)
        ->assertJson(['ok' => true, ]);
}

Это тоже отлично работает. Тем не менее, здесь используются обычные привязки для UserService, и я хочу вместо этого вставить макет / макет.

Я думаю, что мне нужно сменить мой UserService на интерфейс, что легко, но я не уверен, как сказать базовой тестовой системе, что я хочу, чтобы она запускала мой контроллер, но с нестандартной службой. Я вижу, что App::bind() появляется в ответах Stack Overflow при исследовании этого, но App не входит в область действия автоматически в тестах, созданных ремесленниками, так что это похоже на сцепление с соломой.

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

Ответы [ 2 ]

0 голосов
/ 10 мая 2018

Ага, я нашел временное решение. Я опубликую это здесь, а затем объясню, как это можно улучшить.

<?php

namespace Tests\Feature;

use Tests\TestCase;
use \App\Services\Users as UsersService;

class UsersTest extends TestCase
{
    /**
     * Checks the listing of users
     *
     * @return void
     */
    public function testGetUsers()
    {
        $this->app->bind(UsersService::class, function() {
            return new UsersDummy();
        });

        $response = $this->json('GET', '/v1/users');

        $response
            ->assertStatus(200)
            ->assertJson(['ok' => true, ]);
    }
}

class UsersDummy extends UsersService
{
    public function listUsers()
    {
        return ['tom', 'dick', 'harry', ];
    }
}

Это внедряет привязку DI, так что ServiceProvider по умолчанию не должен срабатывать. Если я добавлю некоторый отладочный код в $response, то вот так:

/* @var $response \Illuminate\Http\JsonResponse */
print_r($response->getData(true));

тогда я получаю этот вывод:

Array
(
    [ok] => 1
    [users] => Array
        (
            [0] => tom
            [1] => dick
            [2] => harry
        )

)

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

Далее я выясню, можно ли изменить конструктор моего контроллера с конкретной подсказки реализации (\App\Services\Users) на интерфейс, чтобы моей тестовой реализации не нужно было расширяться от реальной.

0 голосов
/ 10 мая 2018

Очевидным способом является повторная привязка реализации в setUp().

Сделайте себя новым UserTestCase (или отредактируйте тот, что предоставлен Laravel) и добавьте:

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

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

        app()->bind(YourService::class, function() { // not a service provider but the target of service provider
            return new YourFakeService();
        });
    }
}

class YourFakeService {} // I personally keep fakes in the test files itself if they are short

Зарегистрируйте поставщиков условно в зависимости от среды (укажите это в AppServiceProvider.php или в любом другом поставщике, назначенном вами для этой задачи - ConditionalLoaderServiceProvider.php или в любом другом) в методе register()

if (app()->environment('testing')) {
    app()->register(FakeUserProvider::class);
} else {
    app()->register(UserProvider::class);
}

Примечание : недостатком является то, что список провайдеров находится в двух местах, один в config / app.php и один в AppServiceProvider.php

...