对玩具PHP框架的隔离不足的PHPUnit集成测试

时间:2018-11-01 22:08:06

标签: php phpunit thephpleague

我正在做一个小型的个人项目,目的是构建一个玩具框架/拼板样板包装,主要用于教育目的,它实质上只是一系列现成的组件和一些非常基本的胶水将它们粘合在一起。您可以在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。如果我交换前两个测试,则新的第二个测试将失败,这提示我这与拆卸过程有关,但我无法对其进行跟踪下来了。这也可能是我使用路由器的方式(PHP League的Route软件包)。

问题很可能与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 个答案:

没有答案