目录

在hyperf框架的2.2版本的单元测试开启db的事务

/images/posts/在hyperf框架的2.2版本的单元测试开启db的事务/img.png

背景 :

我们团队一直在使用优秀的hyperf框架 , 并在内部推行了单元测试 . 一直使用db的事务回滚的方式在测试环境跑得很顺畅 , 这样不会有脏数据. 最近它更新了2.2版本. 上周升级了下 , 发现hyperf/testing在新版2.2版本下,正常的测试用例跑不下去了 , 定位了一个下午发现问题所在 , 是由于没复用第一次首次初始化的db连接导致的, 考虑到项目比较急 , 没把握一时半会处理完,就先退回2.1版本去了.2版本

今天下午抽了个空把问题修完 , 记录下.

如何在hyperf的testcase中开启mysql的事务处理

最终处理方式

  1. testcase基类, setUp 和 tearDown 分别做 事务开启和事务关闭的逻辑
  2. 在testClient的协程上下文切换的时候 , 塞入已经开启好事务的db连接到到容器中

file test/HttpTestCase.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
# file test/HttpTestCase.php
declare(strict_types=1);
namespace HyperfTest;

use Hyperf\Contract\ConfigInterface;
use Hyperf\DbConnection\Db;
use Hyperf\Utils\ApplicationContext;
use PHPUnit\Framework\TestCase;

/**
 * Class HttpTestCase.
 * @method get($uri, $data = [], $headers = [])
 * @method post($uri, $data = [], $headers = [])
 * @method json($uri, $data = [], $headers = [])
 * @method file($uri, $data = [], $headers = [])
 * @method request($method, $path, $options = [])
 */
abstract class HttpTestCase extends TestCase
{
    /**
     * @var TestClient
     */
    protected $client;

    private $connections = [];

    public function __construct($name = null, array $data = [], $dataName = '')
    {
        parent::__construct($name, $data, $dataName);
    }

    public function __call($name, $arguments)
    {
        return $this->client->{$name}(...$arguments);
    }

    public function setUp(): void
    {
        $container         = ApplicationContext::getContainer();
        $config            = $container->get(ConfigInterface::class);
        $databases_configs = $config->get('databases');
        foreach (array_keys($databases_configs) as $pool_name) {
            $connection = Db::connection($pool_name);
            // "打开DB的事务[{$pool_name}]"
            $connection->isTransaction() || $connection->beginTransaction();
            $this->connections[$pool_name] = $connection;
        }
        $this->client  = make(TestClient::class);
        $this->client->setDbConnection($this->connections); //给TestClient设置好所有的db
    }

    public function tearDown(): void
    {
        //所有db事务rollback
        foreach ($this->connections as $pool_name => $connection) {
            // "回滚DB的事务[{$pool_name}]";
            $connection->rollBack();
        }
    }
}

file test/TestClient.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
# file test/TestClient.php
namespace HyperfTest;

use Hyperf\Contract\ConfigInterface;
use Hyperf\Testing\Client;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class TestClient extends Client
{
    protected $query = [];

    protected $headers = [];

    protected $user;

    private $connections = [];

    public function setDbConnection($connections) {
        $this->connections = $connections;
    }

    protected function persistToContext(ServerRequestInterface $request, ResponseInterface $response)
    {
        // 初始化test client的时候, 会带入的上下文
        parent::persistToContext($request, $response);
        foreach ($this->connections as $pool_name => $connection) {
            $id = $this->getContextKey($pool_name);
            Context::set($id, $connection); // 把打开了事务的connection , 塞入到协程的上下文中
        }
    }
    /**
     * The key to identify the connection object in coroutine context.
     * @param mixed $name
     */
    private function getContextKey($name): string
    {
        return sprintf('database.connection.%s', $name);
    }
}