Испытание изолированного контроллера Laravel 5

Я пытаюсь использовать unit test мои контроллеры в Laravel 5, но у меня серьезные проблемы, обворачивающие его. Похоже, мне приходится торговать большими функциями коротких рук и статическими классами для эквивалентных инъекций эквивалентов, если я действительно хочу выполнить изолированное тестирование.

Во-первых, то, что я вижу в документации как "Unit testing" , не является модульным тестированием для меня. Это похоже на функциональное тестирование. Я не могу протестировать изолированную функцию контроллера, так как мне придется пройти через всю инфраструктуру, и мне нужно будет засеять мою базу данных, если у меня есть код, взаимодействующий с моей базой данных.

Итак, в свою очередь, я хочу протестировать мои контроллеры, изолированные от фреймворка. Это, однако, довольно сложно.

Посмотрим на эту функцию примера (я не использовал некоторые части этой функции для вопроса):

public function postLogin(\Illuminate\Http\Request $request)
{
    $this->validate($request, [
        'email' => 'required|email', 'password' => 'required',
    ]);

    $credentials = $request->only('email', 'password');

    if (Auth::attempt($credentials, $request->has('remember')))
    {
        return redirect()->intended($this->redirectPath());
    }
}

Теперь проблема возникает в заключительных строках. Конечно, я могу издеваться над экземпляром Request, который отправляется функции, и что нет проблем. Но как я буду высмеивать класс Auth или функцию перенаправления? Мне нужно переписать свой класс/функцию с помощью инъекции зависимостей следующим образом:

private $auth;
private $redirector;

public function __construct(Guard $auth, \Illuminate\Routing\Redirector $redirector) 
{
    $this->auth = $auth;
    $this->redirector = $redirector;
}

public function postLogin(\Illuminate\Http\Request $request)
{
    $this->validate($request, [
        'email' => 'required|email', 'password' => 'required',
    ]);

    $credentials = $request->only('email', 'password');

    if ($this->auth->attempt($credentials, $request->has('remember')))
    {
        return $this->redirector->intended($this->redirectPath());
    }
}

И я заканчиваю запутанным unit test, полным mocks:

public function testPostLoginWithCorrectCredentials()
{
    $guardMock = \Mockery::mock('\Illuminate\Contracts\Auth\Guard', function($mock){
        $mock->shouldReceive('attempt')->with(['email' => 'test', 'password' => 'test'], false)->andReturn(true);
    });

    $redirectorMock = \Mockery::mock('\Illuminate\Routing\Redirector', function($mock){
        $mock->shouldReceive('intended')->andReturn('/somePath');
    });

    $requestMock = \Mockery::mock('\Illuminate\Http\Request', function($mock){
        $mock->shouldReceive('only')->with('email', 'password')->andReturn(['email' => 'test', 'password' => 'test']);
        $mock->shouldReceive('has')->with('remember')->andReturn(false);
    });

    $object = new AuthController($guardMock, $redirectorMock);
    $this->assertEquals('/somePath', $object->postLogin($requestMock));
}

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

Мне кажется, что Laravel не обеспечивает то, что я хочу, или моя логика тестирования ошибочна. Могу ли я проверить свои функции контроллера, не выполняя функции тестирования вне контроля и/или имеющую возможность вводить стандартные классы Laravel, доступные мне в любом случае в моем контроллере?

Ответ 1

Вы не должны пытаться использовать контроллеры unit test. Они разработаны для функционального тестирования путем их вызова через протокол HTTP. Это то, как сконструированы контроллеры, и Laravel предоставляет ряд утверждений структуры, которые вы можете включить в свои тесты, чтобы они работали должным образом.

Но если вы хотите unit test код приложения, содержащийся в контроллере, тогда вам действительно нужно использовать Команды.

Использование команд позволяет вам извлечь логику приложения из вашего контроллера в класс. Вы можете unit test класс/команду, чтобы обеспечить ожидаемые результаты.

Затем вы можете просто вызвать команду с контроллера.

Фактически документация Laravel сообщает об этом:

Мы могли бы поместить всю эту логику в метод контроллера; однако это имеет несколько недостатков... сложнее выполнить команду-тестирование команды, так как мы также должны генерировать HTTP-запрос заглушки и сделать полный запрос к приложению для проверки логики покупки подкаста.