为什么returnValueMap()返回NULL

时间:2019-05-12 08:55:48

标签: symfony doctrine-orm phpunit php-7

尝试模拟测试中的理论存储库,当与 findOneBy 方法一起使用时,returnValueMap()始终返回NULL。

我已经模拟了两个实体,然后尝试使用给定的返回值映射来模拟其存储库。测试失败,调试显示returnValueMap()返回NULL。

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

<?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;
    }
}

这是我的考试班:

<?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));
    }
}

2 个答案:

答案 0 :(得分:1)

似乎returnValueMap()testDenormalize()的参数需要用括号括起来以使其成为索引数组。

这是来自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()
    {}
}

答案 1 :(得分:1)

我很难调试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],
            ]);