Недостаточно изолированные тесты интеграции PHPUnit для игрушечного фреймворка PHP - PullRequest
0 голосов
/ 02 ноября 2018

Я работаю над небольшим личным проектом по созданию игрушечного фреймворка / самоуверенного набора шаблонов, в основном для образовательных целей, который, по сути, представляет собой просто серию готовых компонентов и некоторый базовый клей для их склеивания. Вы можете найти его на 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, получая объект ответа обратно.

Есть идеи, почему выдает эту ошибку?

...