Mocking Request в Laravel 5.1 для (фактического) Unit Test
Прежде всего, я знаю docs:
Примечание. Вы не должны высмеивать фасад запроса. Вместо этого передайте требуемый вход в HTTP-вспомогательные методы, такие как вызов и сообщение при выполнении теста.
Но эти тесты больше похожи на интеграцию или функциональность, поскольку, несмотря на то, что вы тестируете контроллер (SUT
), вы не отключаете его из его зависимостей (Request
и других, подробнее об этом позже).
Итак, что я делаю, чтобы сделать правильный цикл TDD
, высмеивает Repository
, Response
и Request
(с которыми у меня проблемы).
Мой тест выглядит следующим образом:
public function test__it_shows_a_list_of_categories() {
$categories = [];
$this->repositoryMock->shouldReceive('getAll')
->withNoArgs()
->once()
->andReturn($categories);
Response::shouldReceive('view')
->once()
->with('categories.admin.index')
->andReturnSelf();
Response::shouldReceive('with')
->once()
->with('categories', $categories)
->andReturnSelf();
$this->sut->index();
// Assertions as mock expectations
}
Это работает отлично, и они следуют стилю Arrange, Act, Assert.
Проблема заключается в Request
, например:
public function test__it_stores_a_category() {
Redirect::shouldReceive('route')
->once()
->with('categories.admin.index')
->andReturnSelf();
Request::shouldReceive('only')
->once()
->with('name')
->andReturn(['name' => 'foo']);
$this->repositoryMock->shouldReceive('create')
->once()
->with(['name' => 'foo']);
// Laravel facades wont expose Mockery#getMock() so this is a hackz
// in order to pass mocked dependency to the controller method
$this->sut->store(Request::getFacadeRoot());
// Assertions as mock expectations
}
Как вы можете видеть, я высмеивал вызов Request::only('name')
. Но когда я запускаю $ phpunit
, я получаю следующую ошибку:
BadMethodCallException: Method Mockery_3_Illuminate_Http_Request::setUserResolver() does not exist on this mock object
Поскольку я не вызываю непосредственно setUserResolver()
из моего контроллера, это означает, что он вызывается непосредственно реализацией Request
. Но почему? Я высмеивал вызов метода, он не должен вызывать никакой зависимости.
Что я делаю неправильно здесь, почему я получаю это сообщение об ошибке?
PS: В качестве бонуса я рассматриваю это неправильно, заставляя TDD с помощью Unit Tests на основе Laravel, поскольку, похоже, документация ориентирована на интеграционное тестирование путем взаимодействия взаимодействия между зависимостями и SUT с помощью $this->call()
?
Ответы
Ответ 1
Единичное тестирование контроллера при использовании Laravel не похоже на отличную идею. Я бы не интересовался отдельными методами, которые вызываются в запросах, ответах и, возможно, даже в классах репозитория, учитывая контекст контроллера.
Кроме того, модульное тестирование контроллера, принадлежащего к структуре, потому что вы хотите отделить тестовый выход от его зависимостей, не имеет смысла, поскольку вы можете использовать этот контроллер только в этой структуре с этими данными.
Поскольку все запросы, ответы и другие классы полностью протестированы (либо через лежащий в основе класс Symfony, либо сам Laravel), как разработчик я буду заниматься только тестированием кода, который у меня есть.
Я бы написал приемочное испытание.
<?php
use App\User;
use App\Page;
use App\Template;
use App\PageType;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CategoryControllerTest extends TestCase
{
use DatabaseTransactions;
/** @test */
public function test__it_shows_a_paginated_list_of_categories()
{
// Arrange
$categories = factory(Category::class, 30)->create();
// Act
$this->visit('/categories')
// Assert
->see('Total categories: 30')
// Additional assertions to verify the right categories can be seen may be a useful additional test
->seePageIs('/categories')
->click('Next')
->seePageIs('/categories?page=2')
->click('Previous')
->seePageIs('/categories?page=1');
}
}
Поскольку в этом тесте используется признак DatabaseTransactions
, легко выполнить аранжировочную часть процесса, что почти позволяет вам прочитать это как псевдо-unit test (но это небольшое растяжение воображения).
Самое главное, этот тест подтверждает, что мои ожидания выполнены. Мой тест называется test_it_shows_a_paginated_list_of_categories
, и моя версия теста делает именно это. Я чувствую, что маршрут unit test только утверждает, что вызывается куча методов, но никогда не проверяет, что я показываю список данных категорий на странице.
Ответ 2
У вас всегда будут проблемы с правильными unit test контроллерами. Я рекомендую приемочное тестирование их с чем-то вроде Codeception. Используя приемочные тесты, вы можете убедиться, что ваши контроллеры/представления обрабатывают любые данные соответствующим образом.
Ответ 3
Я пробовал издеваться над запросом на свои тесты тоже без успеха.
Вот как я проверяю, сохранен ли элемент:
public function test__it_stores_a_category() {
$this->action(
'POST',
'[email protected]',
[],
[
'name' => 'foo',
]
);
$this->assertRedirectedTo('categories/admin/index');
$this->seeInDatabase('categories', ['name' => 'foo']);
}
Надеюсь, что это поможет.