为啥 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->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的主要内容,如果未能解决你的问题,请参考以下文章
为啥我不能使用 populateViewHolder 覆盖方法?