Symfony 4单元测试报错:Detached entity无法删除
嘿,我来帮你排查这个Symfony单元测试的问题~你遇到的Doctrine\ORM\ORMInvalidArgumentException: Detached entity错误,主要有两个核心原因:
问题根源
flush()方法的错误调用
在你的tearDown方法里,你写了$this->em->flush($users)——但Doctrine的EntityManager::flush()根本不需要传入实体集合当参数!当你传入参数时,Doctrine会尝试仅处理这个集合,这会打乱实体的状态管理逻辑,导致部分实体被标记为detached(脱离当前EntityManager的管理)。多个EntityManager实例的冲突
当你在测试中调用static::createClient()时,Symfony会启动一个全新的Kernel和容器实例,这个容器里的EntityManager和你在setUp中获取的不是同一个对象。你在createUser里用第一个EntityManager持久化的User实体,会被Controller里的第二个EntityManager加载处理,回到tearDown时,这些User实体已经不属于当前$this->em的管理范围了,自然会被判定为detached状态,无法直接执行remove操作。
解决方案
方案1:快速修复(修正flush + 合并实体)
先把flush的错误调用改掉,然后在删除前把detached的实体merge回当前EntityManager:
protected function tearDown() { parent::tearDown(); $users = $this->em->getRepository(User::class)->findAll(); foreach ($users as $user) { // 将脱离管理的实体合并回当前EntityManager $user = $this->em->merge($user); $this->em->remove($user); } // flush不需要传参数,默认提交所有待处理的操作 $this->em->flush(); $this->em->close(); $this->em = null; // 避免内存泄漏 }
方案2:更优雅的方式——用事务回滚清理数据
Symfony单元测试里更推荐的做法是用事务包裹每个测试,测试结束后自动回滚,这样完全不需要手动删除数据,还能从根源避免EntityManager冲突的问题:
修改你的setUp和tearDown方法:
protected function setUp() { parent::setUp(); $kernel = self::bootKernel(); $this->em = $kernel->getContainer() ->get('doctrine') ->getManager(); // 为当前测试开启事务 $this->em->beginTransaction(); } protected function tearDown() { parent::tearDown(); // 回滚事务,所有测试产生的数据都不会被提交到数据库 $this->em->rollback(); $this->em->close(); $this->em = null; }
这样每个测试都会在独立的事务中运行,测试结束后所有操作都会被回滚,数据库自动回到初始状态,既省心又彻底解决了实体状态的问题。
另外提个小建议:你的UserController::getAll方法直接把findAll()的结果传给JsonResponse,这可能会导致序列化问题(比如私有属性无法被正确序列化)。建议用Symfony的Serializer组件来处理实体序列化,或者在User实体里实现JsonSerializable接口哦。
内容的提问来源于stack exchange,提问作者MitsiDev

