配置文件

首先看一下 phpunit.xml 的问题

在 2.2 的版本,会出现配置 phpunit.xml 不生效的问题,最终解决的方案是在 test/bootstrap.php 中手动设置 env 配置,大概这样

<?php

declare(strict_types=1);

ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');

error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');

! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);

Swoole\Runtime::enableCoroutine(true);

require BASE_PATH . '/vendor/autoload.php';

Hyperf\Di\ClassLoader::init();

/** @var \Hyperf\Di\Container $container */
$container = require BASE_PATH . '/config/container.php';

// 这样将 env 覆盖进去即可
putenv('APP_ENV=testing');
putenv('DB_DRIVER=sqlite');
putenv('DB_DATABASE=:memory:');
putenv('REDIS_PORT=16380');
putenv('REDIS_PORT_ROOM=16380');
putenv('REDIS_PORT_USER=16380');
$container->get(Hyperf\Contract\ApplicationInterface::class);

DB Driver

然后就是要处理一下 Mock 数据库的问题,由于官方并没有提供 Sqlite 的 Driver,所以需要使用私人包,这里感谢一下 N 老师 composer require fangx/sqlite-driver --dev

基类

由于 Hyperf 并没有提供类似 Laravel 的 RefreshDatabase trait,所以这一部分需要自己实现,包括自动执行 migration

可以构建如下的基类

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     http://gitlab.iusns.com/fox/funbox-server
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace HyperfTest;

/**
 * @internal
 * @coversNothing
 */
abstract class BaseTest extends TestCase
{
    use MakeServiceTrait;

    public function setUp(): void
    {
        $this->command('migrate', [
            '--path' => $this->getMigrationsPath(),
        ]);
        $mockLoggerFactory = \Mockery::mock(LoggerFactory::class, function (MockInterface $mock) {
            $nullLogger = ApplicationContext::getContainer()->get(NullLogger::class);
            $mock->shouldReceive('get')->withAnyArgs()->andReturn($nullLogger);
        });
        /** @var \Hyperf\Di\Container $container */
        $container = ApplicationContext::getContainer();
        $container->set(LoggerFactory::class, $mockLoggerFactory);
        ApplicationContext::setContainer($container);
        parent::setUp();
    }

    protected function tearDown(): void
    {
        ApplicationContext::getContainer()->get(RedisFactory::class)->get('room')->flushAll();
        $this->command('migrate:refresh', [
            '--path' => $this->getMigrationsPath(),
        ]);
    }

    protected function command($command, $parameters = [])
    {
        $app = ApplicationContext::getContainer()
            ->get(ApplicationInterface::class);

        $app->setAutoExit(false);

        return $app->find($command)->run(make(ArrayInput::class, [$parameters]), make(NullOutput::class));
    }

    private function getMigrationsPath()
    {
        return 'migrations/unit_test';
    }
}