为啥 returnValueMap() 返回 NULL

Posted

技术标签:

【中文标题】为啥 returnValueMap() 返回 NULL【英文标题】:Why returnValueMap() is returning NULL为什么 returnValueMap() 返回 NULL 【发布时间】:2019-09-29 13:05:51 【问题描述】:

试图在测试中模拟一个学说存储库,当与 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));
    


【问题讨论】:

【参考方案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;
    

我怀疑该方法总是返回 null,因为未满足条件 $parameterCount !== (\count($map) - 1)。 一个断点证实了我的疑惑,同时也透露$invocation-&gt;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],
            ]);

【讨论】:

【参考方案2】:

看起来testDenormalize() 中的returnValueMap() 的参数需要括号来使其成为索引数组。

这是来自the PHPUnit's document的代码sn-p的略微修改版本:

<?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()
    

【讨论】:

我尝试了解决方案,但它不起作用。实际上,findOneBy() 方法将关联数组作为参数,而不是索引数组。 我的意思是returnValueMap() 应该采用索引数组。虽然我已经确认public function doSomething($parameterA, $parameterB = null) 也会破坏testReturnValueMapWithIndexedArray()。我必须说,这不是超级直观。假设 findOneBy() 来自 Doctrine。

以上是关于为啥 returnValueMap() 返回 NULL的主要内容,如果未能解决你的问题,请参考以下文章

为啥在使用 OpenGL 核心配置文件时会崩溃?

为啥在检查 null 的长度时我没有收到错误 [重复]

为啥我不能使用 populateViewHolder 覆盖方法?

为啥 UITableViewCells cellForRowAt 方法不调用?

为啥python脚本线程不起作用?

为啥我在 Python 中没有得到 333