【问题标题】: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));
    }
}

【问题讨论】:

    标签: symfony doctrine-orm phpunit php-7


    【解决方案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。
      猜你喜欢
      • 2022-12-26
      • 2016-12-28
      • 2015-11-25
      • 2015-07-04
      • 2015-02-25
      • 2015-10-21
      • 2021-08-12
      • 2011-03-18
      • 2017-03-06
      相关资源
      最近更新 更多