You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony/PHPUnit如何测试Doctrine Criteria(或Expression)

在PHPUnit中断言Doctrine Criteria的两种实用方案

方案一:直接拆解表达式结构

通过$c->getWhereExpression()获取根表达式,根据表达式类型(CompositeExpression或Comparison)逐层验证内部逻辑。针对你的场景,测试回调可以这么写:

$this->fileRepository
    ->expects(self::once())
    ->method('matching')
    ->with(self::callback(function (Criteria $c) use ($maxDate) {
        $whereExpr = $c->getWhereExpression();
        // 验证根表达式是AND组合
        if (!$whereExpr instanceof CompositeExpression || $whereExpr->getType() !== CompositeExpression::TYPE_AND) {
            return false;
        }

        $andParts = $whereExpr->getParts();
        // AND包含两个子条件
        if (count($andParts) !== 2) {
            return false;
        }

        // 验证第一个条件:isTemporaryFile = 1
        $tempFileExpr = $andParts[0];
        if (!$tempFileExpr instanceof Comparison || 
            $tempFileExpr->getField() !== 'isTemporaryFile' ||
            $tempFileExpr->getOperator() !== Comparison::EQ ||
            $tempFileExpr->getValue() !== 1) {
            return false;
        }

        // 验证第二个条件:OR组合(creationDate < $maxDate 或 creationDate IS NULL)
        $dateOrExpr = $andParts[1];
        if (!$dateOrExpr instanceof CompositeExpression || $dateOrExpr->getType() !== CompositeExpression::TYPE_OR) {
            return false;
        }

        $orParts = $dateOrExpr->getParts();
        if (count($orParts) !== 2) {
            return false;
        }

        // 验证小于条件
        $ltDateExpr = $orParts[0];
        if (!$ltDateExpr instanceof Comparison ||
            $ltDateExpr->getField() !== 'creationDate' ||
            $ltDateExpr->getOperator() !== Comparison::LT ||
            !$ltDateExpr->getValue()->equals($maxDate)) { // DateTime类型用equals方法比较
            return false;
        }

        // 验证isNull条件
        $isNullExpr = $orParts[1];
        if (!$isNullExpr instanceof Comparison ||
            $isNullExpr->getField() !== 'creationDate' ||
            $isNullExpr->getOperator() !== Comparison::IS_NULL) {
            return false;
        }

        return true;
    }))
    ->willReturn($files);

这种方式完全在内存内完成验证,不需要依赖EntityManager,精准匹配表达式的层级和细节。

方案二:用ExpressionVisitor转换为可断言数组

如果需要更通用的验证逻辑,可以自定义ExpressionVisitor,把Criteria表达式转换成数组结构,再直接断言数组是否匹配预期:

首先定义访问器类:

class CriteriaToArrayVisitor extends ExpressionVisitor
{
    public function walkComparison(Comparison $comparison)
    {
        return [
            'type' => 'comparison',
            'field' => $comparison->getField(),
            'operator' => $comparison->getOperator(),
            'value' => $comparison->getValue() instanceof \DateTimeInterface 
                ? $comparison->getValue()->format(\DateTimeInterface::ATOM) 
                : $comparison->getValue(),
        ];
    }

    public function walkCompositeExpression(CompositeExpression $expr)
    {
        return [
            'type' => 'composite',
            'composite_type' => $expr->getType(),
            'parts' => array_map([$this, 'dispatch'], $expr->getParts()),
        ];
    }

    public function walkLiteral($literal)
    {
        return ['type' => 'literal', 'value' => $literal];
    }
}

然后在测试中使用:

$this->fileRepository
    ->expects(self::once())
    ->method('matching')
    ->with(self::callback(function (Criteria $c) use ($maxDate) {
        $visitor = new CriteriaToArrayVisitor();
        $exprArray = $visitor->dispatch($c->getWhereExpression());

        $expectedArray = [
            'type' => 'composite',
            'composite_type' => CompositeExpression::TYPE_AND,
            'parts' => [
                [
                    'type' => 'comparison',
                    'field' => 'isTemporaryFile',
                    'operator' => Comparison::EQ,
                    'value' => 1,
                ],
                [
                    'type' => 'composite',
                    'composite_type' => CompositeExpression::TYPE_OR,
                    'parts' => [
                        [
                            'type' => 'comparison',
                            'field' => 'creationDate',
                            'operator' => Comparison::LT,
                            'value' => $maxDate->format(\DateTimeInterface::ATOM),
                        ],
                        [
                            'type' => 'comparison',
                            'field' => 'creationDate',
                            'operator' => Comparison::IS_NULL,
                            'value' => null,
                        ],
                    ],
                ],
            ],
        ];

        return $exprArray === $expectedArray;
    }))
    ->willReturn($files);

这种方式扩展性更强,适合复杂的Criteria结构,后续可复用访问器做其他场景的断言。

如果需要验证排序、分页等其他Criteria属性,还可以直接通过$c->getOrderings()、$c->getFirstResult()、$c->getMaxResults()获取对应值并断言。

内容的提问来源于stack exchange,提问作者olinox14

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:18:27