You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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以分为单位存储,表单做了金额单位转换,这部分是正常的。

解决办法

有三种可行方式解决该问题:

  1. 单独断言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'));
    
  2. 为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));
    
  3. 使用PHPUnit的assertEqualsCanonicalizing方法
    该方法忽略对象引用,仅对比对象的属性值:

    $this->assertEqualsCanonicalizing($expected, $model);
    

内容的提问来源于stack exchange,提问作者Galuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 01:45:25