Symfony6中PHPUnit表单测试DateTime字段断言失败问题排查
Symfony 6表单PHPUnit测试中DateTime字段断言失败问题
我在为Symfony 6中包含DateTime对象的表单编写PHPUnit测试时遇到问题:其他字段均正常,但DateTime字段导致assertEquals对比Product对象时断言失败,尽管表单已经同步。
测试代码
public function testSubmitValidData(): void { $date = new \DateTime('2023-06-05 00:00:00'); $formData = [ 'name' => 'test name', 'description' => 'test description', 'price'=>100, 'purchased_at' => $date ]; $model = new Product(); $form = $this->factory->create(ProductType::class, $model); $expected = new Product(); $expected->setPurchasedAt($date); $expected->setName('test name'); $expected->setDescription('test description'); $expected->setPrice(10000); $form->submit($formData); // 这行断言通过 $this->assertTrue($form->isSynchronized()); // 这行因DateTime对象断言失败 $this->assertEquals($expected, $model); }
测试结果
1) App\Tests\Form\Type\TestedTypeTest::testSubmitValidData Failed asserting that two objects are equal. --- Expected +++ Actual @@ @@ 'name' => 'test name' 'description' => 'test description' 'price' => 10000 - 'purchased_at' => DateTime Object (...) 'user' => null 'users' => Doctrine\Common\Collections\ArrayCollection Object (...) )
ProductType日期字段配置
->add('purchased_at', DateTimeType::class, array( 'label' => false, 'attr' => array( 'class' => 'min date', 'required' => false, 'id' => 'purchased_at')))
问题原因及解决办法
原因
Symfony表单的DateTimeType在处理提交的DateTime对象时,会创建一个新的DateTime实例,而非直接复用你传入的对象。这导致$expected里的purchased_at是手动创建的原对象,而$model里的是表单处理后生成的新对象——尽管两个DateTime的时间值完全相同,但它们是不同的对象实例,assertEquals默认对比对象引用,因此断言失败。
另外测试中formData的price为100,expected的price为10000,这应该是实体里的price以分为单位存储,表单做了金额单位转换,这部分是正常的。
解决办法
有三种可行方式解决该问题:
单独断言DateTime字段的值
不直接对比整个Product对象,单独验证purchased_at的时间一致性:// 对比时间戳 $this->assertEquals($expected->getPurchasedAt()->getTimestamp(), $model->getPurchasedAt()->getTimestamp()); // 或者对比格式化后的字符串 $this->assertEquals($expected->getPurchasedAt()->format('Y-m-d H:i:s'), $model->getPurchasedAt()->format('Y-m-d H:i:s'));为Product类实现自定义相等判断方法
在Product实体中编写equals方法,让PHPUnit按属性值(包括DateTime的时间值)判断对象是否相等:class Product { // ... 其他属性与方法 public function equals(self $other): bool { if ($this === $other) { return true; } return $this->name === $other->name && $this->description === $other->description && $this->price === $other->price && ( ($this->purchased_at === null && $other->purchased_at === null) || ( $this->purchased_at instanceof \DateTimeInterface && $other->purchased_at instanceof \DateTimeInterface && $this->purchased_at->getTimestamp() === $other->purchased_at->getTimestamp() ) ); } }测试中调用该方法断言:
$this->assertTrue($expected->equals($model));使用PHPUnit的
assertEqualsCanonicalizing方法
该方法忽略对象引用,仅对比对象的属性值:$this->assertEqualsCanonicalizing($expected, $model);
内容的提问来源于stack exchange,提问作者Galuk
相关产品推荐
相关产品推荐

