如何对 createQueryBuilder 进行单元测试

How to unit test createQueryBuilder

我有这个方法:

public function findAllBy(?string $categoryId): array
    {
        $qb = $this->entityManager->createQueryBuilder()
            ->select('p')
            ->from(Product::class, 'p');

        if (null !== $categoryId) {
            $qb->from(Category::class, 'cc')
               ->join(CategoryProduct::class, 'cp', Join::WITH, 'p.id = cp.product_id')
               ->join(Category::class, 'c', Join::WITH, 'cp.category_id = c.id')
               ->where('cc.id = :id')
               ->andWhere('cc.left <= c.left')
               ->andWhere('cc.right >= c.right')
               ->setParameter('id', $categoryId, 'uuid');
        }

        return $qb->getQuery()->getResult();
    }

我正在尝试以这种方式进行测试(显然不是正确的):

public function testFindAllProductsByFilters():void
    {
        $entityRepositoryMock = $this->createMock(EntityRepository::class);
        $entityManagerMock = $this->createMock(EntityManagerInterface::class);
        $entityManagerMock->expects($this->once())
            ->method('getRepository')
            ->with(Product::class)
            ->willReturn($entityRepositoryMock);
        $entityManagerMock->expects($this->once())
            ->method('createQueryBuilder')
            ->willReturn($entityRepositoryMock);

        $this->repository = new DoctrineProductRepository($entityManagerMock);
        $this->assertIsArray($this->repository->findAllBy(ProductFactory::CATEGORY_ID_FIRST));
    }

我得到的错误: 1)

App\Tests\Unit\Infrastructure\Domain\Model\Product\DoctrineProductRepositoryTest::testFindAllProductsByFilters Error: Call to a member function from() on null

这段代码甚至可以通过单元测试进行测试吗?

你应该not mock what you don't own,我的建议是避免在这种情况下进行单元测试。 此外,我不会使用(滥用)模拟(通常是测试替身),因为您正在测试实现而不是 SUT 的行为。

让我们看一个例子

class Foo()
{
  public function doSomething(): int
  {
    // some heavy logic here
  }
}

class Bar()
{
  public function doSomething(Foo $foo): int
  {
    $result = $foo->doSomething();
    // other elaboration upon $result
  } 
}

当然,这是故意设计的一个微不足道的例子。如果你在 Bar 测试中使用测试替身,你会写类似`

$fooMock->expects($this->once())
  ->method('doSomething')
  ->willReturn(1);

假设 Foo 将其更改为 public API,将 doSomething 重命名为 doSomethingElse。发生什么了?即使 Foo 行为根本没有改变,您也需要更改测试。

如前所述,这是一个微不足道的例子,但应该会给你一个想法。