Недостаточно изолированные тесты интеграции PHPUnit для игрушечного фреймворка PHP

Я работаю над небольшим личным проектом по созданию игрушечного фреймворка / шаблонного пакета, в основном для образовательных целей, который, по сути, представляет собой просто серию готовых компонентов и некоторый базовый клей для их склеивания. Вы можете найти это на Github. Тем не менее, у меня возникли некоторые проблемы с добавлением интеграционных тестов - в прошлом я обычно полагался на то, что мои фреймворки уже работают на меня.

Мне пришлось пропустить один тест сейчас, потому что он проходит только если выполняется в отдельном процессе. Это наводит меня на мысль, что с разрывом что-то не так, но я не смог понять, что это такое.

Есть базовый тестовый пример, который выглядит так:

<?php declare(strict_types = 1);

namespace Tests;

use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

class TestCase extends \PHPUnit\Framework\TestCase
{
    use MockeryPHPUnitIntegration;

    public function setUp(): void
    {
        if (!defined('BASE_DIR')) {
            define('BASE_DIR', __DIR__.'/../');
        }
        $this->app = new \App\Kernel;
        $this->app->bootstrap();
        $this->container = $this->app->getContainer();
    }

    public function tearDown(): void
    {
        $this->app = null;
        $this->container = null;
    }
}

Затем интеграционный тестовый пример, который расширяет его и выглядит так:

<?php declare(strict_types = 1);

namespace Tests;

use Zend\Diactoros\ServerRequest;

class IntegrationTestCase extends TestCase
{
    public function makeRequest(string $uri, string $method = 'GET', $server = [], $files = [], $body = 'php://input', $headers = [], $cookies = [], $queryParams = [], $parsedBody = null): IntegrationTestCase
    {
        $request = new ServerRequest(
            $server,
            $files,
            $uri,
            $method,
            $body,
            $headers,
            $cookies,
            $queryParams,
            $parsedBody
        );
        $this->response = $this->app->handle($request);
        return $this;
    }

    public function assertStatusCode(int $code, $message = ''): void
    {
        if (!isset($this->response)) {
            throw new \Exception('No response has been received');
        }
        self::assertThat($this->response->getStatusCode() == $code, self::isTrue(), $message);
    }

    public function tearDown(): void
    {
        $this->response = null;
        parent::tearDown();
    }
}

Наконец, фактический класс интеграционного теста выглядит следующим образом:

<?php declare(strict_types = 1);

namespace Tests\Integration;

use Tests\IntegrationTestCase;

class ExampleTest extends IntegrationTestCase
{
    public function testHome(): void
    {
        $this->makeRequest('/')
            ->assertStatusCode(200);
    }

    public function testLogin(): void
    {
        $this->markTestSkipped();
        $this->makeRequest('/login')
            ->assertStatusCode(200);
    }

    public function test404(): void
    {
        $this->makeRequest('/foo')
            ->assertStatusCode(404);
    }
}

Второй тест (в настоящее время пропущенный), когда он запускается один или в отдельном процессе, работает нормально. Тем не менее, при нормальном запуске второй тест выдает 404. Если я поменяю первые два теста на другой, новый второй провалится, что говорит мне о том, что это как-то связано с процессом разборки, но я не смог его отследить вниз еще. Это также может быть то, как я использую роутер (пакет Route от PHP League).

Проблема вполне может быть связана с Kernel класс, воспроизведенный ниже:

<?php declare(strict_types = 1);

namespace App;

use Zend\Diactoros\ServerRequestFactory;
use League\Container\Container;
use League\Container\ReflectionContainer;
use League\Route\Strategy\ApplicationStrategy;
use Psr\Http\Message\RequestInterface;

/**
 * Application kernel
 */
class Kernel
{
    /**
     * @var Container
     */
    private $container;

    /**
     * @var Router
     */
    private $router;

    /**
     * @var Providers
     */
    private $providers = [
        'App\Providers\ContainerProvider',
        'App\Providers\CacheProvider',
        'App\Providers\DoctrineProvider',
        'App\Providers\EventProvider',
        'App\Providers\FlysystemProvider',
        'App\Providers\HandlerProvider',
        'App\Providers\LoggerProvider',
        'App\Providers\RouterProvider',
        'App\Providers\SessionProvider',
        'App\Providers\ShellProvider',
        'App\Providers\TwigProvider',
    ];

    /**
     * Bootstrap the application
     *
     * @return Kernel
     */
    public function bootstrap(): Kernel
    {
        $this->setupContainer();
        $this->setupRoutes();
        $this->setErrorHandler();
        return $this;
    }

    /**
     * Handle a request
     *
     * @param RequestInterface $request HTTP request.
     * @return void
     */
    public function handle(RequestInterface $request): \Psr\Http\Message\ResponseInterface
    {
        try {
            $response = $this->router->dispatch($request, $this->container->get('response'));
        } catch (\League\Route\Http\Exception\NotFoundException $e) {
            $twig = $this->container->get('Twig_Environment');
            $tpl = $twig->load('404.html');
            $response = $this->container->get('response')->withStatus(404);
            $response->getBody()->write($tpl->render());
        }
        return $response;
    }

    private function setupContainer(): void
    {
        $container = new Container;
        $container->delegate(
            new ReflectionContainer
        );

        foreach ($this->providers as $provider) {
            $container->addServiceProvider($provider);
        }
        $container->share('emitter', \Zend\Diactoros\Response\SapiEmitter::class);
        $container->share('response', \Zend\Diactoros\Response::class);
        $container->share('Psr\Http\Message\ResponseInterface', \Zend\Diactoros\Response::class);
        $this->container = $container;
    }

    private function setErrorHandler(): void
    {
        error_reporting(E_ALL);
        $environment = getenv('APP_ENV');

        $whoops = new \Whoops\Run;
        if ($environment !== 'production') {
            $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler);
        } else {
            $handler = $this->container->get('App\Contracts\Exceptions\Handler');
            $whoops->pushHandler(new \Whoops\Handler\CallbackHandler($handler));
        }
        $whoops->register();
    }

    private function setupRoutes(): void
    {
        $strategy = (new ApplicationStrategy)->setContainer($this->container);
        $router = $this->container->get('League\Route\Router')
            ->setStrategy($strategy);
        require_once BASE_DIR.'/routes.php';
        $this->router = $router;
    }

    public function getContainer(): Container
    {
        return $this->container;
    }
}

Этот класс является ядром базового шаблона - он запускается с помощью вызова bootstrap() метод, после которого вы передаете ему объект запроса Zend Diactoros, получая ответный объект обратно.

Любая идея, почему это вызывает эту ошибку?

0 ответов

Другие вопросы по тегам