如何确定 PHPUnit 模拟失败的原因?

How to determine why a PHPUnit mock fails?

我想在 class 之后进行单元测试。

<?php
namespace Gpx\Handlers;
use Gpx\EntityInfrastructure\Model\Events\SessionInvalidated;
use Gpx\EntityInfrastructure\Model\Payload;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\SynchronousHandlerInterface;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\MessageHandlingContextInterface;
use Gpx\HfxEventSourcing\HfxAggregateRoot;
use Gpx\HfxEventSourcing\HfxProjectionHelper;
use Gpx\HfxEventSourcing\HfxEventMetadata;
use Gpx\HfxEventSourcing\HfxRepository;
use Gpx\Hfx\MessageTransport\Response\SendableResponse;

class BroadcastSessionInvalidated implements SynchronousHandlerInterface
{

    /** @var HfxRepository  */
    private $repository;

    /** @var  HfxProjectionHelper */
    private $projectionHelper;


    public function __construct(HfxRepository $repository, HfxProjectionHelper $projectionHelper)
    {
        $this->repository = $repository;
        $this->projectionHelper = $projectionHelper;
    }


    public function handleSynchronousMessage(MessageHandlingContextInterface $context): SendableResponse
    {
        $content = $context->message();
        $header = $context->rawMessage()->header();

        $metadata = HfxEventMetadata::fromHfxHeader($header);
        $payload = Payload::fromMessageContent($content);

        /** @var HfxAggregateRoot $roleAggregate */
        // Gpx\HfxEventSourcing\HfxAggregateRoot
        $roleAggregate = $this->repository->get($payload->id());
        $roleAggregate->registerEvent(SessionInvalidated::class, $payload, $metadata);

        $this->repository->save($roleAggregate);
        $currentEvent = $roleAggregate->currentEvent();
        $context->sendNonBlockingAsynchronous('session_invalidated',$content);

        $this->projectionHelper->updateReadModel();
        return SendableResponse::answerTo($context->rawMessage(), 1100, [
            'responseMessage' => 'Success',
            'event' => $currentEvent
        ]);
    }
}

到目前为止我写的测试用例

<?php

namespace Gpx\Tests\Feature;

use Ramsey\Uuid\Uuid;
use Gpx\Json\JsonEncode;
use Prophecy\Argument;
use PHPUnit\Framework\TestCase;
use Gpx\HfxEventSourcing\Hfx;
use Gpx\HfxEventSourcing\HfxRepository;
use Gpx\Hfx\Framework\MessageTransportApplication\Handler\MessageHandlingContextInterface;
use Gpx\Handlers\BroadcastSessionInvalidated;
use Gpx\Hfx\MessageTransport\Message\ReceivedMessage;
use Prooph\EventSourcing\AggregateRoot;
use Gpx\HfxEventSourcing\HfxAggregateRoot;


class BroadcastSessionInvalidatedTest extends TestCase
{
    /** @var HfxRepository */
    private $repository;

    /** @var  Hfx */
    private $projectionHelper;

    // We have to test handleSynchronousMessage handler whether it is returning sendable response with certain properties in it.
    public function testHandleSynchronousMessage()
    {

        // Expected return value of message() function of $context
        $expectedReturnValue = [
            "session_id" => "1a92-4376-a8eb-deaf208e1",
            "user_id" => "we",
            "access_jwt" => "C",
            "access_token" => "john@gmail.com",
            "refresh_token" => "C",
            "refresh_token_expires" => "john@gmail.com"
        ];

        // Expected return value of rawMessage() function of $context
        $headerResponseExpected = [
            'header' => [
                'version' => '2.0',
                'originId' => (string)Uuid::uuid4(),
                'destination' => 'application/meta@1.0.0',
                'sent' => '2017-12-19T10:12:37.941+00:00'
            ],
            'content' => [
                'session_id' => "8365526e-fb92-4376-a8eb-deaf208edf61",
                'title' => "A task's title."
            ]
        ];

        // Prophecy means prediction of the future object

        // Prediction of $context object starts
        $context = $this->prophesize(MessageHandlingContextInterface::class);
        $context->message(Argument::any())->willReturn($expectedReturnValue);

        $encodedMessage = new JsonEncode($headerResponseExpected);
        $rawMessage = ReceivedMessage::fromEncodedMessage($encodedMessage->asString());
        $context->rawMessage()->willReturn($rawMessage);

        $context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))
            ->shouldBeCalled();
        // Prediction of $context object ends

        // Repository Mocking Starts
        $ravenAggregateRoot = $this->getMockBuilder(HfxAggregateRoot::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->ravenRepository = $this->prophesize(HfxRepository::class);
        $this->ravenRepository->get('1a92-4376-a8eb-deaf208e1')->shouldBeCalled()->willReturn($ravenAggregateRoot);
        $this->ravenRepository->save(Argument::any())->shouldBeCalled();
        // Repository Mocking Ends

        // Mocking Hfx and calling the method updateReadModel which will return the string UpdateReadModel
        $this->projectionHelper = $this->createMock(Hfx::class);
        $this->projectionHelper->method('updateReadModel')
            ->willReturn('UpdateReadModel');

        // Actual calling
        $broadcastPlatformSessionInvalidated = new BroadcastSessionInvalidated($this->ravenRepository->reveal(), $this->projectionHelper);
        //$broadcastPlatformSessionInvalidated = new BroadcastSessionInvalidated($this->ravenRepository, $this->projectionHelper);
        $response = $broadcastPlatformSessionInvalidated->handleSynchronousMessage($context->reveal());

        $this->assertInstanceOf('Gpx\Hfx\MessageTransport\Response\SendableResponse', $response);
        $this->assertArrayHasKey("responseMessage", $response->content()->data());
        $this->assertArrayHasKey("event", $response->content()->data());
        $this->assertEquals("Success", $response->content()->data()['responseMessage']);


    }
}

当我执行测试时它抛出一个错误

PHPUnit 6.5.4 by Sebastian Bergmann and contributors.
.E                                                                  2 / 2 (100%)
Time: 506 ms, Memory: 6.00MB
There was 1 error:
1) Gpx\Tests\Feature\BroadcastSessionInvalidatedTest::testHandleSynchronousMessage
Prophecy\Exception\Call\UnexpectedCallException: Method call:
  - sendNonBlockingAsynchronous("session_invalidated", ["session_id" => "1a92-4376-a8eb-deaf208e1", "user_id" => "we", "access_jwt" => "C", "access_token" => "john@gmail.com", "refresh_token" => "C", "refresh_token_expires" => "john@gmail.com"])
on Double\MessageHandlingContextInterface\P3 was not expected, expected calls were:
  - message(*)
  - rawMessage()
  - sendNonBlockingAsynchronous(exact("platform_session_initiated"), type(array))
/vagrant/services/sessions-stream/app/src/Gpx/Handlers/BroadcastSessionInvalidated.php:54
/vagrant/services/sessions-stream/app/tests/Feature/BroadcastPlatformSessionInvalidatedTest.php:107
ERRORS!
Tests: 2, Assertions: 6, Errors: 1.

我这里做错了什么?

Exception 告诉你哪里出了问题:你使用 shouldBeCalled() 来告诉 PHPUnit 检查方法 sendNonBlockingAsynchronous() 被调用时第一个参数 platform_session_initiatedarray 在第二个参数.

// BroadcastSessionInvalidatedTest
$context->sendNonBlockingAsynchronous('platform_session_initiated', Argument::type("array"))
        ->shouldBeCalled();

满足第二个条件,但是在你的实际方法中第一个参数是session_invalidated:

// BroadcastSessionInvalidated
$context->sendNonBlockingAsynchronous('session_invalidated',$content);

所以将期望更改为

// BroadcastSessionInvalidatedTest
$context->sendNonBlockingAsynchronous('session_invalidated', Argument::type("array"))
        ->shouldBeCalled();

它会起作用。