Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Тесты

Тест — код, который сам проверяет приложение: отправляет запрос и сравнивает ответ с ожидаемым. Приложение Slim — объект, который принимает запрос и возвращает ответ, поэтому веб-сервер для тестов не нужен: запрос создают прямо в коде. Здесь и пригодится файл config/bootstrap.php, который возвращает приложение, не запуская его.

Установка

composer require --dev phpunit/phpunit

Ключ --dev помечает пакет как нужный только при разработке.

Где лежат тесты

Создайте в корне проекта папку tests и файл настроек phpunit.xml:

<phpunit bootstrap="vendor/autoload.php" colors="true">
    <testsuites>
        <testsuite name="app">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

Файл сообщает PHPUnit, где искать тесты. Имена файлов с тестами заканчиваются на Test.php.

Сессия в консоли

Тесты запускаются из консоли, а там session_start() выдаёт предупреждение. Поэтому сначала поправьте условие в SessionMiddleware:

// src/Middleware/SessionMiddleware.php
if (PHP_SAPI !== 'cli' && session_status() !== PHP_SESSION_ACTIVE) {

PHP_SAPI равен cli, когда PHP запущен из консоли. В тестах страниц сайта перед запросом задают $_SESSION = []; — посредники, читающие сессию, получат пустой массив.

Первый тест

// tests/ApiPostsTest.php
use PHPUnit\Framework\TestCase;
use Slim\Psr7\Factory\ServerRequestFactory;
 
final class ApiPostsTest extends TestCase
{
    public function testUnknownAddressReturns404(): void
    {
        $app = require __DIR__ . '/../config/bootstrap.php';
 
        $request = (new ServerRequestFactory())->createServerRequest('GET', '/api/nothing');
        $response = $app->handle($request);
 
        $this->assertSame(404, $response->getStatusCode());
    }
}

ServerRequestFactory создаёт запрос с нужным методом и адресом. Метод handle() проводит его через все посредники и маршруты и возвращает ответ, ничего не выводя. Запуск:

vendor/bin/phpunit

Команда напечатает OK (1 test, 1 assertion).

База для тестов

Тест не должен трогать настоящую базу. До первого запроса запись PDO::class в контейнере заменяют базой SQLite в памяти — она создаётся пустой и исчезает после теста. Добавьте в класс теста метод:

// tests/ApiPostsTest.php
private function createApp(): \Slim\App
{
    $app = require __DIR__ . '/../config/bootstrap.php';
 
    $pdo = new PDO('sqlite::memory:', null, null, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    $pdo->exec('CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT, body TEXT, created_at TEXT)');
    $pdo->exec("INSERT INTO posts (title, body) VALUES ('Тестовая статья', 'Текст')");
 
    $app->getContainer()->set(PDO::class, $pdo);
 
    return $app;
}

Метод set() контейнера PHP-DI подменяет запись. Репозиторий получит поддельное подключение и не заметит разницы.

Проверка ответа JSON

// tests/ApiPostsTest.php
public function testListReturnsPosts(): void
{
    $request = (new ServerRequestFactory())->createServerRequest('GET', '/api/posts');
    $response = $this->createApp()->handle($request);
 
    $data = json_decode((string) $response->getBody(), true);
 
    $this->assertSame(200, $response->getStatusCode());
    $this->assertSame('Тестовая статья', $data['data'][0]['title']);
}

Тело ответа — поток, поэтому перед разбором его приводят к строке.

Запрос с данными

// tests/ApiPostsTest.php
public function testCreateRequiresToken(): void
{
    $request = (new ServerRequestFactory())
        ->createServerRequest('POST', '/api/posts')
        ->withParsedBody(['title' => 'Новая статья', 'body' => 'Текст']);
 
    $this->assertSame(401, $this->createApp()->handle($request)->getStatusCode());
}

Заголовки добавляют методом withHeader(), данные — withParsedBody(). Без токена посредник отвечает кодом 401, и статья не создаётся.

Частая ошибка

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

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.