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
相关产品推荐
相关产品推荐

