Symfony 3.3中EntityManagerInterface服务的PHPUnit单元测试及传参方法
对依赖EntityManagerInterface的Symfony服务进行单元测试
在Symfony里测试依赖EntityManager的服务,单元测试的核心思路是模拟(Mock)EntityManagerInterface——完全隔离真实数据库和容器依赖,不用启动整个Symfony应用。下面我用实际例子一步步教你怎么做:
1. 先看咱们的目标服务示例
假设你有一个处理产品逻辑的ProductManager服务,它的构造函数依赖EntityManagerInterface:
// src/Service/ProductManager.php namespace App\Service; use Doctrine\ORM\EntityManagerInterface; use App\Entity\Product; class ProductManager { private $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } // 保存产品的方法 public function saveProduct(Product $product): void { $this->entityManager->persist($product); $this->entityManager->flush(); } // 根据ID获取产品的方法 public function getProductById(int $id): ?Product { return $this->entityManager->getRepository(Product::class)->find($id); } }
2. 编写单元测试类
创建对应的测试类(放在tests/Unit/Service目录下),继承PHPUnit的TestCase,核心是用PHPUnit自带的MockBuilder生成EntityManagerInterface的模拟对象,然后注入到服务中。
示例1:测试持久化方法(saveProduct)
这个场景里,我们只需要验证服务是否正确调用了EntityManager的persist和flush方法:
// tests/Unit/Service/ProductManagerTest.php namespace App\Tests\Unit\Service; use App\Service\ProductManager; use App\Entity\Product; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; class ProductManagerTest extends TestCase { public function testSaveProduct(): void { // 第一步:创建EntityManagerInterface的Mock对象 $entityManagerMock = $this->createMock(EntityManagerInterface::class); // 第二步:设置Mock的方法预期——我们期望persist被调用一次,参数是Product实例 $entityManagerMock->expects($this->once()) ->method('persist') ->with($this->isInstanceOf(Product::class)); // 同样,期望flush也被调用一次 $entityManagerMock->expects($this->once()) ->method('flush'); // 第三步:实例化咱们的服务,把Mock的EntityManager传进去 $productManager = new ProductManager($entityManagerMock); // 第四步:创建测试用的Product实体(这里用真实实体也可以,或者Mock) $product = new Product(); $product->setName('Test Product'); // 第五步:调用服务的方法 $productManager->saveProduct($product); // 这里不需要额外断言,因为上面的expects已经验证了方法调用是否符合预期 } }
示例2:测试查询方法(getProductById)
如果服务需要调用EntityManager的getRepository,那我们得同时模拟Repository对象,因为实际查询是Repository做的:
public function testGetProductById(): void { // 第一步:创建一个Mock的Product实体(或者用真实实体,看你需要) $mockProduct = $this->createMock(Product::class); $mockProduct->method('getId')->willReturn(1); // 第二步:创建ProductRepository的Mock,并设置find方法的返回值 $productRepositoryMock = $this->createMock(\Doctrine\ORM\EntityRepository::class); $productRepositoryMock->expects($this->once()) ->method('find') ->with(1) ->willReturn($mockProduct); // 第三步:创建EntityManager的Mock,让它返回上面的Repository Mock $entityManagerMock = $this->createMock(EntityManagerInterface::class); $entityManagerMock->expects($this->once()) ->method('getRepository') ->with(Product::class) ->willReturn($productRepositoryMock); // 第四步:实例化服务并调用方法 $productManager = new ProductManager($entityManagerMock); $result = $productManager->getProductById(1); // 第五步:断言结果符合预期 $this->assertInstanceOf(Product::class, $result); $this->assertEquals(1, $result->getId()); }
3. 几个关键要点
- 完全隔离依赖:单元测试就是要测试服务本身的逻辑,所以绝对不能用真实的EntityManager(那是功能测试的范畴),必须用Mock替代。
- Mock的预期设置:通过
expects()可以指定方法被调用的次数,with()指定参数,willReturn()指定返回值,这样就能验证服务是否正确和依赖交互。 - 复杂场景扩展:如果你的服务用到了QueryBuilder,那你需要继续Mock QueryBuilder对象,逐层模拟下去,确保每个依赖都被隔离。
- 运行测试:在终端执行
php bin/phpunit tests/Unit/Service/ProductManagerTest.php就能运行这个测试类了。
内容的提问来源于stack exchange,提问作者JumpingJack_2
相关产品推荐
相关产品推荐

