Введение
Laravel построен с учётом тестирования. Фактически, поддержка PHPUnit доступна по умолчанию, а файл phpunit.xml уже настроен для вашего приложения. Также фреймворк содержит удобные методы для полноценного тестирования ваших приложений.
Папка tests содержит файл с примером теста ExampleTest.php. После установки нового приложения Laravel просто выполните команду shphpunit
для запуска ваших тестов.
Среда
При выполнении тестов Laravel автоматически задаст настройки среды testing. Также при тестировании Laravel автоматически настроит сессии и кэш на драйвер array, а значит данные сессий и кэша не сохранятся при тестировании.
При необходимости вы можете определить другие значения настроек для тестовой среды. Переменные среды testing можно настроить в файле phpunit.xml, но перед запуском тестов не забудьте очистить кэш настроек с помощью Artisan-команды shconfig:clear
!
Создание и выполнение тестов
Для создания теста используйте Artisan-команду shmake:test
:
shphp artisan make:test UserTest
Эта команда поместит новый класс UserTest в папку tests. Далее вы можете объявлять методы тестов как вы обычно объявляете их для PHPUnit. Для запуска тестов просто выполните команду shphpunit
в терминале:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UserTest extends TestCase
{
/**
* Пример базового теста.
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
}
}
Если вы определили собственный метод PHPsetUp()
, не забудьте вызвать PHPparent::setUp()
.
Начиная с версии Laravel 5.3 последующие разделы данной статьи вынесены в отдельные статьи Тестирование приложения, Тестирование БД и Заглушки. — прим. пер.
Тестирование приложения
Laravel предоставляет очень удобный API для создания HTTP-запросов к вашему приложению, проверки вывода, и даже заполнения форм. Например, посмотрим на файл ExampleTest.php в папке tests:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* Пример базового теста функции.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5')
->dontSee('Rails');
}
}
Метод PHPvisit()
делает GET-запрос в приложение. Метод PHPsee()
объявляет, что мы должны увидеть данный текст в отклике приложения. Метод PHPdontSee()
объявляет, что данный текст не возвращается в отклике приложения. Это самый базовый тест приложения в Laravel.
Взаимодействие с приложением
Само собой, можно делать намного больше, чем просто проверять появление текста в данном отклике. Давайте рассмотрим несколько примеров нажатия ссылок и заполнения форм.
В этом тесте мы сделаем запрос в приложение, «нажмём» ссылку в возвращённом отклике, а затем проверим, что оказались на нужном URI. Например, предположим, что в нашем отклике есть ссылка с текстом «О нас»:
xml<a href="/about-us">О нас</a>
Теперь давайте напишем тест, нажимающий ссылку и проверяющий переход пользователя на правильную страницу:
public function testBasicExample()
{
$this->visit('/')
->click('О нас')
->seePageIs('/about-us');
}
Также Laravel предоставляет несколько методов для тестирования форм. Методы PHPtype()
, PHPselect()
, PHPcheck()
, PHPattach()
и PHPpress()
позволяют вам взаимодействовать со всеми элементами ввода на ваших формах. Например, представим, что на странице регистрации в приложении есть такая форма:
xml<form action="/register" method="POST"> {{ csrf_field() }} <!--<p> для версии 5.1 и ранее: {!! csrf_field() !!} </p>--> <div> Name: <input type="text" name="name"> </div> <div> <input type="checkbox" value="yes" name="terms"> Accept Terms </div> <div> <input type="submit" value="Register"> </div> </form>
Мы можем написать тест для заполнения этой формы и проверки результата:
public function testNewUserRegistration()
{
$this->visit('/register')
->type('Taylor', 'name')
->check('terms')
->press('Register')
->seePageIs('/dashboard');
}
Если ваша форма содержит другие элементы ввода, такие как радио-кнопки и выпадающие списки, вы так же легко можете заполнить такие типы полей. Вот список всех методов для работы с формами:
Метод | Описание |
---|---|
$this->type($text, $elementName) | Ввести текст в данное поле |
$this->select($value, $elementName) | Выбрать радио-кнопку или выпадающее поле |
$this->check($elementName) | Поставить чекбокс |
$this->uncheck($elementName) | Снять чекбокс (для 5.2 и выше) |
$this->attach($pathToFile, $elementName) | Прикрепить файл к форме |
$this->press($buttonTextOrElementName) | Нажать кнопку с заданным текстом или именем |
Если на вашей форме есть элементы ввода типа file, вы можете прикрепить файлы к форме методом PHPattach()
:
public function testPhotoCanBeUploaded()
{
$this->visit('/upload')
->type('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
Тестирование JSON API
Также Laravel предоставляет несколько вспомогательных функций для тестирования JSON API и их откликов. Например, методы PHPget()
, PHPpost()
, PHPput()
, PHPpatch()
и PHPdelete()
используются для выполнения различных HTTP-запросов. Вы также легко можете передать данные и заголовки в эти методы. Для начала давайте напишем тест, выполняющий POST-запрос к /user и проверяющий, что данный массив возвращается в формате JSON:
<?php
class ExampleTest extends TestCase
{
/**
* Пример базового теста функции.
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
Метод PHPseeJson()
конвертирует данный массив в JSON, а затем проверяет, что фрагмент JSON появляется где-либо внутри полного JSON-отклика, возвращаемого приложением. Поэтому, если в нём будут ещё и другие свойства, этот тест всё равно будет пройден успешно, так как данный фрагмент присутствует в отклике.
Проверка точного совпадения JSON
Если вы хотите проверить точное совпадение данного массива с возвращённым из приложения JSON, вам надо использовать метод PHPseeJsonEquals()
:
<?php
class ExampleTest extends TestCase
{
/**
* Пример базового теста функции.
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
добавлено в 5.2 ()
Также можно проверить соответствие структуры JSON определённым требованиям. Для этого служит используйте метод PHPseeJsonStructure()
и передайте в него список (вложенных) ключей:
<?php
class ExampleTest extends TestCase
{
/**
* Пример базового функционального теста.
*
* @return void
*/
public function testBasicExample()
{
$this->get('/user/1')
->seeJsonStructure([
'name',
'pet' => [
'name', 'age'
]
]);
}
}
В этом примере ожидается получение name и вложенного объекта pet с его собственными name и age. Метод PHPseeJsonStructure()
выполнится без ошибки, если в отклике будут дополнительные ключи. Например, проверка будет пройдена и в том случае, когда pet будет иметь атрибут weight.
Вы можете использовать *, чтобы задать требование, что возвращаемая структура JSON должна содержать список, в котором каждый элемент содержит по крайней мере те атрибуты, которые есть в наборе значений:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
// Требование, что каждый user в списке должен иметь как минимум атрибуты id, name и email.
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email'
]
]);
}
}
Вы можете использовать вложение *. Тогда мы потребуем, что каждый user в JSON отклике должен содержать заданный набор атрибутов, и каждый pet каждого user также должен содержать заданный набор атрибутов:
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email', 'pets' => [
'*' => [
'name', 'age'
]
]
]
]);
Сессии / Аутентификация
В Laravel есть несколько функций для работы с сессиями во время тестирования. Сначала вы можете задать данные сессии для данного массива при помощи метода PHPwithSession()
. Это полезно для загрузки сессии с данными перед выполнением тестового запроса в приложение:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
Конечно, чаще всего сессии используют для задания нужного состояния пользователя, например, аутентифицированный пользователь. Простой способ аутентифицировать данного пользователя в качестве текущего — метод PHPactingAs()
. Например, мы можем использовать фабрику модели, чтобы сгенерировать и аутентифицировать пользователя:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$user = factory(App\User::class)->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
добавлено в 5.2 ()
Отключение посредников
При тестировании приложения иногда удобно отключить посредников для некоторых тестов. Это позволит вам тестировать маршруты и контроллер изолированно от любых влияний посредников. Laravel содержит простой типаж WithoutMiddleware, который можно использовать для автоматического отключения всех посредников для класса теста:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use WithoutMiddleware;
//
}
Если захотите отключить посредников только для некоторых тестовых методов, можете вызвать метод PHPwithoutMiddleware()
из тестовых методов:
<?php
class ExampleTest extends TestCase
{
/**
* Пример базового теста функции.
*
* @return void
*/
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
}
Свои HTTP-запросы
Если вы хотите сделать свой HTTP-запрос в приложение и получить полный объект Illuminate\Http\Response, используйте метод PHPcall()
:
public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
Если вы делаете запросы POST, PUT или PATCH, то можете передать массив входных данных вместе с запросом. Тогда эти данные будут доступны в ваших маршрутах и контроллере через экземпляр запроса:
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
Проверки PHPUnit (assertions)
Laravel предоставляет несколько assert-методов для тестов PHPUnit:
Метод | Описание |
---|---|
->assertResponseOk(); | Проверка того, что клиентский отклик имеет статус ОК |
->assertResponseStatus($code); | Проверка того, что клиентский отклик имеет указанный статус. |
->assertViewHas($key, $value = null); | Проверка того, что представление в отклике содержит данный кусок привязанных данных. |
->assertViewHasAll(array $bindings); | Проверка того, что представление содержит данный список привязанных данных. |
->assertViewMissing($key); | Проверка того, что в представлении в отклике отсутствует данный кусок привязанных данных. |
->assertRedirectedTo($uri, $with = []); | Проверка того, что клиент был переадресован по данному URI |
->assertRedirectedToRoute($name, $parameters = [], $with = []); | Проверка того, что клиент был переадресован по данному маршруту |
->assertRedirectedToAction($name, $parameters = [], $with = []); | Проверка того, что клиент был переадресован к данному действию |
->assertSessionHas($key, $value = null); | Проверка того, что в сессии есть данное значение. |
->assertSessionHasAll(array $bindings); | Проверка того, что в сессии есть данный список значений. |
->assertSessionHasErrors($bindings = [], $format = null); | Проверка того, что в сессии есть привязанные ошибки. |
->assertHasOldInput(); | Проверка того, что в сессии есть введённые ранее данные. |
->assertSessionMissing($key); | Проверка того, что в сессии нет указанного ключа (для версии 5.2 и выше). |
добавлено в 5.0 ()
Вызов контроллера из теста
Вы также можете вызвать из теста любой контроллер:
$response = $this->action('GET', 'HomeController@index');
$response = $this->action('GET', 'UserController@profile', ['user' => 1]);
Вам не надо указывать полное пространство имён контроллера при использовании метода PHPaction()
. Укажите только ту часть, которая идёт за App\Http\Controllers.
Метод PHPgetContent()
вернёт содержимое-строку ответа. Если ваш маршрут вернёт PHPView
, вы можете получить его через свойство PHP$original
:
$view = $response->original;
$this->assertEquals('John', $view['name']);
Для вызова HTTPS-маршрута можно использовать метод PHPcallSecure()
:
$response = $this->callSecure('GET', 'foo/bar');
Работа с базами данных
Также Laravel содержит различные полезные инструменты для упрощения тестирования приложений, использующих БД. Во-первых, вы можете использовать функцию PHPseeInDatabase ()
для проверки наличия в БД данных, подходящих по набору критериев. Например, если мы хотим проверить, что в таблице users есть запись со значением в поле email равным sally@example.com, то можем сделать следующее:
public function testDatabase()
{
// Сделать вызов в приложение...
$this->seeInDatabase('users', ['email' => 'sally@example.com']);
}
Само собой, метод PHPseeInDatabase()
и другие подобные методы служат для удобства. Но вы можете использовать любые встроенные в PHPUnit методы проверок для дополнения своих тестов.
Сброс базы данных после каждого теста
Часто бывает полезно сбрасывать БД после каждого теста, чтобы данные из предыдущего теста не попадали в последующие тесты.
Один из вариантов — откатывать БД после каждого теста и мигрировать её перед следующим тестом. В Laravel есть простой типаж DatabaseMigrations, который автоматически сделает это за вас. Просто используйте типаж в классе теста:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
/**
* Пример базового теста функции.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
Другой вариант — обернуть каждый тест в транзакцию БД. И снова, в Laravel есть удобный типаж DatabaseTransactions, который автоматически сделает это за вас:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseTransactions;
/**
* Пример базового теста функции.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
Этот типаж обернёт в транзакцию только то соединения с БД, которое используется по умолчанию.
Фабрики моделей
При тестировании часто необходимо вставить несколько записей в БД перед выполнением теста. Вместо указания значений каждого поля тестовых данных вручную, Laravel позволяет определить набор атрибутов для каждой из ваших моделей Eloquent при помощи «фабрик». Для начала посмотрим на файл database/factories/ModelFactory.php в вашем приложении. Изначально этот файл содержит одно определение фабрики:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
В замыкание, которое выступает в качестве определения фабрики, вы можете вернуть тестовые значения по умолчанию для всех атрибутов модели. Замыкание получит экземпляр PHP-библиотеки Faker, которая позволяет вам удобно генерировать случайные данных различных типов для тестирования.
Конечно, вы можете добавить свои собственные дополнительные фабрики в файл ModelFactory.php. А для улучшения организации вы можете также создать файлы дополнительной фабрики. Например, вы можете создать файлы UserFactory.php и CommentFactory.php в папке database/factories.
Иногда вам необходимо иметь несколько фабрик для одного класса модели Eloquent. Например, если нужна фабрика для пользователей «Administrator» вдобавок к обычным пользователям. Эти фабрики можно определить методом PHPdefineAs()
:
$factory->defineAs(App\User::class, 'admin', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => str_random(10),
'remember_token' => str_random(10),
'admin' => true,
];
});
Вместо дублирования всех атрибутов из вашей основной фабрики пользователя, вы можете использовать метод PHPraw()
для получения базовых атрибутов. Когда у вас есть атрибуты, просто дополните их любыми необходимыми дополнительными значениями:
$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
$user = $factory->raw(App\User::class);
return array_merge($user, ['admin' => true]);
});
Когда вы определили свои фабрики, вы можете использовать их в своих тестах или файлах для заполнения БД, чтобы генерировать экземпляры модели с помощью глобальной функции PHPfactory()
. Давайте рассмотрим несколько примеров создания моделей. Сначала используем метод PHPmake()
, который создаёт модели, но не сохраняет их в БД:
public function testDatabase()
{
$user = factory(App\User::class)->make();
// Использование модели в тестах...
}
Если вы хотите переопределить некоторые из значений по умолчанию для своих моделей, вы можете передать массив значений в метод PHPmake()
. Будут заменены только указанные значения, а остальные будут иметь значения, определённые в фабрике:
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
Также вы можете создать коллекцию моделей или создать модели заданного типа:
// Создать три экземпляра App\User...
$users = factory(App\User::class, 3)->make();
// Создать экземпляр App\User "admin"...
$user = factory(App\User::class, 'admin')->make();
// Создать три экземпляра App\User "admin"...
$users = factory(App\User::class, 'admin', 3)->make();
Метод PHPcreate()
не только создаёт экземпляры модели, но также сохраняет их в БД при помощи Eloquent-метода PHPsave()
:
public function testDatabase()
{
$user = factory(App\User::class)->create();
// Использование модели в тестах...
}
Вы можете переопределить атрибуты для модели, передав массив в метод PHPcreate()
:
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
Вы можете сохранить в БД даже несколько моделей. В данном примере мы даже прикрепим к созданным моделям отношение. При использовании метода PHPcreate()
для создания нескольких моделей возвращается экземпляр коллекции, позволяя вам использовать любые удобные функции для работы с коллекцией, такие как PHPeach()
:
$users = factory(App\User::class, 3)
->create()
->each(function ($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
добавлено в 5.2 ()
Отношения и атрибуты замыкания
Также вы можете прикрепить к моделям отношения с помощью атрибутов замыканий в определении вашей фабрики. Например, если вы хотите создать экземпляр User при создании Post, можно сделать так:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
}
];
});
Эти замыкания также получают подготовленный массив атрибутов фабрики, который содержит их:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
},
'user_type' => function (array $post) {
return App\User::find($post['user_id'])->type;
}
];
});
Заглушки
Заглушки событий
Если вы используете систему событий Laravel, то при желании можете отключить или заглушить определённые события при тестировании. Например, при тестировании регистрации пользователя вам, вероятно, не нужно вызывать обработчики всех событий UserRegistered, поскольку они могут посылать письма «добро пожаловать» и т.п.
В Laravel есть удобный метод PHPexpectsEvents()
, который проверяет возникновение ожидаемых событий, но предотвращает запуск всех обработчиков для этих событий:
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->expectsEvents(App\Events\UserRegistered::class);
// Тестирование регистрации пользователя...
}
}
добавлено в 5.2 ()
Методом PHPdoesntExpectEvents()
можно проверить, что заданные события не произошли:
<?php
class ExampleTest extends TestCase
{
public function testPodcastPurchase()
{
$this->expectsEvents(App\Events\PodcastWasPurchased::class);
$this->doesntExpectEvents(App\Events\PaymentWasDeclined::class);
// Тестирование покупки подкаста...
}
}
Если вы хотите предотвратить запуск всех обработчиков событий, используйте метод PHPwithoutEvents()
:
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->withoutEvents();
// Тестирование кода регистрации пользователя...
}
}
Заглушки задач
Иногда вам может понадобиться просто проверить, что определённые задачи запускаются вашими контроллерами при выполнении запросов в ваше приложение. Это позволяет вам тестировать ваши маршруты / контроллеры изолированно — отдельно от логики вашей задачи. А саму задачу вы можете протестировать в отдельном тест-классе.
В Laravel есть удобный метод PHPexpectsJobs()
, который проверяет, что ожидаемые задачи вызываются, но при этом сами задачи выполняться не будут:
<?php
class ExampleTest extends TestCase
{
public function testPurchasePodcast()
{
$this->expectsJobs(App\Jobs\PurchasePodcast::class);
// Тестирование кода покупки подкаста...
}
}
Этот метод обнаруживает только те задачи, которые запускаются методами типажа DispatchesJobs или вспомогательной функцией PHPdispatch()
. Он не обнаружит задачу, которая отправлена напрямую в PHPQueue::push
.
Заглушки фасадов
При тестировании вам может потребоваться отловить вызов к одному из фасадов Laravel. Например, рассмотрим такое действие контроллера:
<?php
namespace App\Http\Controllers;
use Cache;
//для версии 5.1 и ранее:
//use Illuminate\Routing\Controller;
class UserController extends Controller
{
/**
* Показать список всех пользователей приложения.
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
Вы можете отловить вызов фасада Cache с помощью метода PHPshouldReceive()
, который вернёт экземпляр объекта-заглушки Mockery. Поскольку фасады извлекаются и управляются сервис-контейнером Laravel, их намного проще тестировать, чем обычный статический класс. Например, давайте отловим наш вызов фасада Cache:
<?php
class FooTest extends TestCase
{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
}
Не делайте этого для фасада Request. Вместо этого, передайте желаемый ввод вспомогательному HTTP-методу, такому как PHPcall()
или PHPpost()
, во время выполнения вашего теста.
добавлено в 5.0 ()
Рассмотрим такое действие контроллера:
public function getIndex()
{
Event::fire('foo', ['name' => 'Dayle']);
return 'All done!';
}
Вы можете отловить вызов класса Event с помощью метода PHPshouldReceive()
этого фасада, который вернёт экземпляр объекта-заглушки Mockery.
public function testGetIndex()
{
Event::shouldReceive('fire')->once()->with('foo', ['name' => 'Dayle']);
$this->call('GET', '/');
}
Не делайте этого для объекта Request. Вместо этого, передайте желаемый ввод методу PHPcall()
во время выполнения вашего теста.
Вспомогательные методы
Класс TestCase содержит несколько вспомогательных методов для упрощения тестирования вашего приложения.
Установка и очистка сессий из теста
$this->session(['foo' => 'bar']);
$this->flushSession();
Установка текущего авторизованного пользователя
Вы можете установить текущего авторизованного пользователя с помощью метода PHPbe()
:
$user = new User(['name' => 'John']);
$this->be($user);
Заполнение БД тестовыми данными
Вы можете заполнить вашу БД начальными данными из теста методом PHPseed()
:
$this->seed();
$this->seed('DatabaseSeeder');
Больше информации на тему начальных данных доступно в разделе о миграциях.
Обновление приложения
Как вы уже возможно знаете, вы можете получить доступ к сервис-контейнеру вашего приложения с помощью PHP$this->app
из любого тестового метода. Этот экземпляр сервис-контейнера обновляется для каждого тестового класса. Если вы хотите вручную обновить приложение для определённого метода, вы можете использовать метод PHPrefreshApplication()
из этого тестового метода. Это приведет к сбросу дополнительных привязок, таких как заглушки, которые были помещены в сервис-контейнер после запуска теста.
Комментарии (1)
Нужно исправить /5.3/ на /v5/ в ссылках:
Тестирование приложения, Тестирование БД и Заглушки.