Пытаясь имитировать репозиторий доктрин внутри теста, returnValueMap() всегда возвращает NULL при использовании с методом найтиOneBy.
Я издевался над двумя объектами, а затем пытался издеваться над их репозиторием с заданной картой возвращаемых значений. Тест не пройден, и отладка показывает, что 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));
}
}




Похоже, параметр returnValueMap() в testDenormalize() нуждается в квадратных скобках, чтобы сделать его индексированным массивом.
Вот слегка измененная версия фрагмента кода из документ PHPUnit:
<?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()
{}
}
Я имел в виду, что returnValueMap() должен принимать индексированный массив. Хотя я подтвердил, что public function doSomething($parameterA, $parameterB = null) также сломается testReturnValueMapWithIndexedArray(). Должен сказать, это не суперинтуитивно. Предположим, что findOneBy() взято из Doctrine.
Мне было трудно отлаживать библиотеку 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],
]);
Я попробовал решение, но оно не работает. На самом деле метод findOneBy() принимает в качестве параметра ассоциативный массив, а не индексированный.