为什么 returnValueMap() 返回 NULL

Why returnValueMap() is returning NULL

试图在测试中模拟学说存储库,returnValueMap() 在与 findOneBy 方法一起使用时总是 returning NULL。

我模拟了两个实体,然后尝试使用给定的 return 值映射模拟它们的存储库。测试失败,调试显示 returnValueMap() 为 returning NULL。

这是要测试的class(反规范化器)

<?php

declare(strict_types=1);

namespace App\Serializer;

use App\Entity\AdditionalService;
use App\Repository\AdditionalServiceRepository;
use Dto\AdditionalServiceCollection;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class AdditionalServiceCollectionDenormalizer implements DenormalizerInterface
{
    /** @var AdditionalServiceRepository */
    private $additionalServiceRepository;

    public function __construct(AdditionalServiceRepository $additionalServiceRepository)
    {
        $this->additionalServiceRepository = $additionalServiceRepository;
    }

    public function denormalize($mappedCsvRow, $class, $format = null, array $context = [])
    {
        $addtionalServicesCollection = new AdditionalServiceCollection();
        foreach ($mappedCsvRow as $fieldName => $fieldValue) {
            /** @var AdditionalService $additionalService */
            $additionalService = $this->additionalServiceRepository->findOneBy(['name'=>$fieldName]);

            if ($additionalService) {
                $addtionalServicesCollection->add($additionalService->getId(), $fieldValue);
            }
        }

        return $addtionalServicesCollection;
    }

    public function supportsDenormalization($data, $type, $format = null)
    {
        return $type instanceof  AdditionalServiceCollection;
    }
}

这是我的测试class:

<?php

namespace App\Tests\Import\Config;

use App\Entity\AdditionalService;
use App\Repository\AdditionalServiceRepository;
use App\Serializer\AdditionalServiceCollectionDenormalizer;
use PHPUnit\Framework\TestCase;
use Dto\AdditionalServiceCollection;

class AddionalServiceCollectionDenormalizerTest extends TestCase
{
    public function provider()
    {
        $expected = new AdditionalServiceCollection();
        $expected->add(1, 22.1)->add(2, 3.1);

        return [
            [['man_1' => 22.1], $expected],
            [['recycling' => 3.1], $expected],
        ];
    }

    /**
     * @dataProvider provider
     * @covers \App\Serializer\AdditionalServiceCollectionDenormalizer::denormalize
     */
    public function testDenormalize(array $row, AdditionalServiceCollection $exptected)
    {
        $manOneService = $this->createMock(AdditionalService::class);
        $manOneService->expects($this->any())->method('getId')->willReturn(1);

        $recycling = $this->createMock(AdditionalService::class);
        $recycling->expects($this->any())->method('getId')->willReturn(2);

        $additionalServicesRepoMock = $this
            ->getMockBuilder(AdditionalServiceRepository::class)
            ->setMethods(['findOneBy'])
            ->disableOriginalConstructor()
            ->getMock();
        $additionalServicesRepoMock
            ->expects($this->any())
            ->method('findOneBy')
            ->will($this->returnValueMap(
                [
                    ['name'=>['man_1'], $manOneService],
                    ['name'=>['recycling'], $recycling],
                ]
            ));

        $denormalizer = new AdditionalServiceCollectionDenormalizer($additionalServicesRepoMock);

        self::assertEquals($exptected, $denormalizer->denormalize($row, AdditionalServiceCollection::class));
    }
}

testDenormalize() 中的 returnValueMap() 的参数似乎需要括号才能成为索引数组。

这里是 the PHPUnit's document 中的代码片段的略微修改版本:

<?php

namespace App\Tests;

use PHPUnit\Framework\TestCase;

class ReturnValueMapTest extends TestCase
{
    public function testReturnValueMapWithAssociativeArray()
    {
        $stub = $this->createMock(SomeClass::class);

        $map = [
            [
                'name' => ['man_1'],
                'Hello'
            ],
        ];

        $stub->method('doSomething')
            ->will($this->returnValueMap($map));

        // This will fail as doSomething() returns null
        $this->assertSame('Hello', $stub->doSomething(['name' => ['man_1']]));
    }

    public function testReturnValueMapWithIndexedArray()
    {
        $stub = $this->createMock(SomeClass::class);

        $map = [
            [
                ['name' => ['man_1']], // Notice the difference
                'Hello'
            ],
        ];

        $stub->method('doSomething')
            ->will($this->returnValueMap($map));

        $this->assertSame('Hello', $stub->doSomething(['name' => ['man_1']]));
    }
}

class SomeClass
{
    public function doSomething()
    {}
}

我费了好大劲调试PHPUnit库,终于弄明白是findOneBy()方法需要两个参数,其中第二个是可选的(设置为null)

willReturnMap()方法如下:

/**
 * Stubs a method by returning a value from a map.
 */
class ReturnValueMap implements Stub
{
    /**
     * @var array
     */
    private $valueMap;

    public function __construct(array $valueMap)
    {
        $this->valueMap = $valueMap;
    }

    public function invoke(Invocation $invocation)
    {
        $parameterCount = \count($invocation->getParameters());

        foreach ($this->valueMap as $map) {
            if (!\is_array($map) || $parameterCount !== (\count($map) - 1)) {
                continue;
            }

            $return = \array_pop($map);

            if ($invocation->getParameters() === $map) {
                return $return;
            }
        }

        return;
    }

我怀疑由于未满足条件 $parameterCount !== (\count($map) - 1),该方法总是返回 null。 一个断点证实了我的疑惑,也揭示了 $invocation->getParameters() 转储如下:

array(2) {
  [0] =>
  array(1) {
    'name' =>
    string(5) "man_1"
  }
  [1] =>
  NULL
}

因此,我不得不明确地传递 null 作为第二个参数。 所以最终工作地图必须是:

$this->additionalServicesRepoMock
            ->method('findOneBy')
            ->willReturnMap([
                [['name' => 'man_1'], null, $manOneService],
                [['name' => 'recycling'], null, $recyclingService],
            ]);