【问题标题】:How to mock variables in a PHPUnit Coverage Test?如何在 PHPUnit 覆盖测试中模拟变量?
【发布时间】:2022-01-27 14:14:57
【问题描述】:

我正在编写 PHPUnit 测试并运行覆盖测试。我承认很难达到 100% 的覆盖率,但是,我想尽可能接近。在以下场景中,如何在子句中模拟变量以测试代码块?

class CalendarClientService
{
    /** @var array SCOPES */
    public const SCOPES = [Google_Service_Calendar::CALENDAR];

    /** @var string ACCESS_TYPE */
    public const ACCESS_TYPE = "offline";

    /** @var string CALENDAR_ID */
    public const CALENDAR_ID = "primary";

    /** @var int MAX_RESULTS */
    public const MAX_RESULTS = 25;

    /** @var string ORDER_BY */
    public const ORDER_BY = "startTime";

    /** @var bool SINGLE_EVENTS */
    public const SINGLE_EVENTS = true;

    /** @var string|null TIME_MIN */
    public const TIME_MIN = null;

    /** @var bool CACHE_TIME_TO_LIVE */
    public const CACHE_TIME_TO_LIVE = 604800;

    /** @var string */
    public string $clientSecretPath = "";

    /** @var StorageAdapterFactoryInterface */
    protected StorageAdapterFactoryInterface $storageAdapterFactory;

    /** @var StorageInterface */
    protected StorageInterface $storageInterfaceCache;

    /**
     * CalendarClientService constructor.
     * @param string $clientSecretPath
     * @param StorageAdapterFactoryInterface $storageAdapterFactory
     * @param StorageInterface $storageInterfaceCache
     */
    public function __construct(
        string $clientSecretPath,
        StorageAdapterFactoryInterface $storageAdapterFactory,
        StorageInterface $storageInterfaceCache
    ) {
        $this->clientSecretPath = $clientSecretPath;
        $this->storageAdapterFactory = $storageAdapterFactory;
        $this->storageInterfaceCache = $storageInterfaceCache;
    }

    /** @return string */
    public function getClientSecretPath()
    {
        return $this->clientSecretPath;
    }

    /** @param string $secretFile */
    public function setClientSecretPath(string $secretFile)
    {
        $this->clientSecretPath = $secretFile;
    }

    /**
     * @param array
     * @return Google_Service_Calendar_Event
     */
    public function getGoogleServiceCalendarEvent($eventData)
    {
        return new Google_Service_Calendar_Event($eventData);
    }

    /**
     * @param string
     * @return Google_Service_Calendar_EventDateTime
     */
    public function getGoogleServiceCalendarEventDateTime($dateTime)
    {
        $eventDateTime = new Google_Service_Calendar_EventDateTime();
        $eventDateTime->setDateTime(Carbon::parse($dateTime)->toW3cString());
        $eventDateTime->setTimeZone(Carbon::parse($dateTime)->timezone->getName());
        return $eventDateTime;
    }

    /**
     * @param Google_Client $client
     * @return Events
     */
    public function getGoogleServiceCalendarResourceEvents(Google_Client $client)
    {
        $service = new Google_Service_Calendar($client);
        return $service->events;
    }

    /**
     * @param int
     * @return array
     * @throws Exception
     * @throws ExceptionInterface
     */
    public function getEventData($id)
    {
        $client = $this->getClient();
        if (!$this->authenticateClient($client)) {
            return [
                "error" => "authentication",
                "url" => filter_var($client->createAuthUrl(), FILTER_SANITIZE_URL),
            ];
        }
        $service = $this->getGoogleServiceCalendarResourceEvents($client);
        return ["event" => $service->get(self::CALENDAR_ID, $id)];
    }

    /**
     * @return Google_Client
     * @throws Exception
     */
    public function getClient()
    {
        $client = new Google_Client();
        $client->setApplicationName(Module::MODULE_NAME);
        $client->setScopes(self::SCOPES);
        $client->setAuthConfig($this->clientSecretPath);
        $client->setAccessType(self::ACCESS_TYPE);
        return $client;
    }

    /**
     * @param Google_Client $client
     * @return bool
     * @throws ExceptionInterface
     */
    public function authenticateClient(Google_Client $client)
    {
        if ($this->storageInterfaceCache->hasItem("api_access_token")) {
            $accessToken = json_decode($this->storageInterfaceCache->getItem("api_access_token"), true);
            if ($accessToken["error"] == "invalid_grant" || empty($accessToken)) {
                $this->storageInterfaceCache->removeItem("api_access_token");
            } else {
                $this->storageInterfaceCache->setItem("api_access_token", json_encode($accessToken));
                $client->setAccessToken($accessToken);
            }
        }
        if ($client->isAccessTokenExpired()) {
            $tokenValid = false;
            if ($client->getRefreshToken()) {
                $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
                $accessToken = $client->getAccessToken();
                $this->storageInterfaceCache->setItem("api_access_token", json_encode($accessToken));
                $tokenValid = true;
            } else {
                $helper = new Helper();
                if(!$helper->verifyAuthCode($_GET["code"])){
                    return $tokenValid;
                }
                $authCode = $_GET["code"];
                $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
                if ($accessToken["error"] == "invalid_grant" || empty($accessToken)) {
                    $this->storageInterfaceCache->removeItem("api_access_token");
                } else {
                    $this->storageInterfaceCache->setItem("api_access_token", json_encode($accessToken));
                    $client->setAccessToken($accessToken);
                    $tokenValid = true;
                }
            }
        } else {
            $tokenValid = true;
        }

        return isset($tokenValid) ? $tokenValid : false;
    }

我想在 authenticateClient 方法中测试从顶部开始的第 6 行,并想模拟这个子句 $accessToken["error"] == "invalid_grant" || empty($accessToken)。

现在怎么办?

编辑:这是我写的一个测试。现在无论我在$this->storageInterfaceCacheMock->method("getItem") 中模拟什么值,它总是返回空$accessToken。我还附上了图片,以便更好地了解正在发生的事情以及我想要什么。

    public function testGetEventDataReturnsArrayOnSuccessfulAuthenticateClientThroughCache()
    {
        $this->storageInterfaceCacheMock->method("hasItem")->willReturn(true);
        $this->storageInterfaceCacheMock->method("getItem")->willReturn(json_encode('{"access_token":"ya29.a0ARrdaM99pJTf1XzmD1ngxAH3XJud8lvHb0aTaOOABYdfdhsdfgsdfgVD9OoH4heiKoskDF7DMkHj1_aPuWIO5TE14KHJidFf66xwn_pTCkkSow6Kg4lRHwGrNQBQGI8sPlgnFO5U5hJvYdqgxDMHEqw1TER2w","expires_in":3599,"scope":"https:\/\/www.googleapis.com\/auth\/calendar","token_type":"Bearer","created":1637312218,"refresh_token":"1\/\/03psr5omKiljUCgYFDHDGJHGSHSNwF-L9Iraor5zcfe-h3BeCHSFGSDFGDGJHjy4UnEtKj974LXthS5bWexQcjviVGfJsdfGHSHgIrDn6Yk"}'));

        $this->assertIsArray($this->calendarClientService->getEventData(1));
    }

下面提到了另一个未按要求执行的测试。 (也可以在屏幕截图中看到)

    public function testAccessTokenIsExpiredAndGotRefreshToken()
    {
        $this->googleClientMock->method("isAccessTokenExpired")->willReturn(true);
        $this->googleClientMock->method("getRefreshToken")->willReturn(true);
        $this->googleClientMock->method("fetchAccessTokenWithRefreshToken")->willReturnSelf();
        $this->googleClientMock->method("getAccessToken")->willReturnSelf();
        $this->assertTrue($this->calendarClientService->authenticateClient($this->googleClientMock));
    }

【问题讨论】:

    标签: phpunit code-coverage


    【解决方案1】:

    我相信你想要json_decode你不需要在这里双重编码:

         $this->storageInterfaceCacheMock->method("getItem")->willReturn(json_encode('{"access_token":"...."}'));
    

    只是

    $this->storageInterfaceCacheMock->method("getItem")->willReturn('{"access_token":"...."}');
    

    【讨论】:

    • 原始类CalendarClientService 已经将此方法的输出传递给json_decode,这就是原因。 $accessToken = json_decode($this->storageInterfaceCache->getItem("api_access_token"), true); 编码后存储到缓存中。 json_encode($accessToken)
    • @GoharSahi 我更新了答案,据我了解,您对其进行了双重编码。
    • 我的担忧是一样的。要测试的类CalendarClientService 具有storageInterfaceCache->getItem 正在返回一个json_encode 项目,该项目随后是json_decode。 storageInterfaceCacheMock 不是应该返回相同的,即 json_encoded 访问令牌,所以原始类然后是 json_decode 本身?我试过你的建议,但没有奏效。
    【解决方案2】:

    这是我解决问题并让测试成功的方法。

    我如下声明了一个虚拟ACCESS_TOKEN,然后在测试方法中使用。

    class CalendarControllerTest extends AbstractApplicationTestCase
    {
        /** @var string CLIENT_SECRET */
        public const CLIENT_SECRET = __DIR__ . "/../_fixtures/config/client_secret.json";
    
        /** @var string CLIENT_SECRET */
        public const ACCESS_TOKEN = [
            "access_token" => "test-data",
            "expires_in" => 3592,
            "scope" => "https://www.googleapis.com/auth/calendar",
            "token_type" => "Bearer",
            "created" => 1640858809,
        ];
        ...
        ...
       ...
    }
    
        public function setUp(): void
        {
            parent::setUp();
            $this->googleClientMock = $this->getMockBuilder(Google_Client::class)
                ->disableOriginalConstructor()
                ->onlyMethods(
                    [
                        "isAccessTokenExpired",
                        "setAuthConfig",
                        "getRefreshToken",
                        "fetchAccessTokenWithRefreshToken",
                        "fetchAccessTokenWithAuthCode",
                        "getAccessToken",
                        "setAccessToken",
                    ]
                )
                ->getMock();
            $this->googleServiceCalendarResourceEventsMock = $this->getMockBuilder(Events::class)
                ->disableOriginalConstructor()
                ->onlyMethods(["get"])
                ->getMock();
            $this->googleServiceCalendarEventMock = $this->getMockBuilder(Event::class)
                ->disableOriginalConstructor()
                ->getMock();
            $this->storageInterfaceCacheMock = $this->getMockForAbstractClass(StorageInterface::class);
    
            $this->container->setAllowOverride(true);
            $this->container->setService(Google_Client::class, $this->googleClientMock);
            $this->container->setService(Events::class, $this->googleServiceCalendarResourceEventsMock);
            $this->container->setService(Event::class, $this->googleServiceCalendarEventMock);
            $this->container->setService(StorageInterface::class, $this->storageInterfaceCacheMock);
            $this->container->setAllowOverride(true);
    
            $this->googleClientMock->method("setAuthConfig")->willReturn(true);
    
            $this->calendarClientService = $this->container->get("ServiceManager")->get(CalendarClientService::class);
            $this->calendarClientService->setClientSecretPath(CalendarControllerTest::CLIENT_SECRET);
        }
        
        /** @throws ExceptionInterface */
        public function testAccessTokenIsExpiredAndFailureToRefreshTokenWillGenerateNewAccessToken()
        {
            $this->calendarClientService->setAuthCode(CalendarControllerTest::DEFAULT_TESTING_VALUE);
            $this->googleClientMock->method("isAccessTokenExpired")->willReturn(true);
            $this->googleClientMock->method("getRefreshToken")->willReturn(false);
            $this->googleClientMock->method("fetchAccessTokenWithAuthCode")->willReturn(
                CalendarControllerTest::ACCESS_TOKEN
            );
            $this->storageInterfaceCacheMock->method("setItem")->willReturn(true);
            $this->googleClientMock->method("setAccessToken")->willReturnSelf();
    
            $this->assertTrue($this->calendarClientService->authenticateClient($this->googleClientMock));
        }
    

    【讨论】:

      猜你喜欢
      • 2021-01-07
      • 2020-08-09
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      • 1970-01-01
      • 2020-02-11
      • 2021-02-08
      • 2015-11-02
      相关资源
      最近更新 更多