2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PHPAdvent Calendar 2021

Day 9

PHPUnitでGeneratorをMockしたい場合ってどうすればよいの?

Last updated at Posted at 2021-12-09

こんにちは! @takoba といいます。インターネットと音楽とカレーライスを主にたべるぺちぱーです🍛
現在は コネヒト株式会社 でサーバーサイドを主戦場にしたエンジニアとして働いています。 CakePHP を主に触っています〜〜🙋‍♀️

このエントリは、 PHP Advent Calendar 2021 の9日目のエントリです。
8日目は @kubotak さんによる PHPコードを消すライブラリを作った でした。

前提

Generator is 何?

cf. PHP: ジェネレータとは - Manual

ジェネレータを使うと、foreach でデータ群を順に処理するコードを書くときに メモリ内で配列を組み立てなくても済むようになります。 メモリ内で配列を組み立てると memory_limit を越えてしまうかもしれないし、 無視できないほどの時間がかかってしまうかもしれません。 配列を作る代わりに、ジェネレータ関数を書くことになります。これは通常の 関数と同じものですが、 ジェネレータ関数は一度だけ return するのではなく、必要に応じて何度でも yield することができます。 つまり、値を繰り返し返せるということです。

要は、 return の代わりに yield を使うことで、都度、処理の結果を返すことができます。 map 的な処理をする場合に便利そうですね。

例えば、以下のような処理があるとします。

function squareArray(array $arr): array
{
    $res = [];
    foreach ($arr as $v) {
        array_push($res, $v ** 2);
    }

    return $res;
}

foreach (squareArray([1, 2, 3, 4, 5]) as $squared) {
    echo $squared . ',';
}
#=> 1,4,9,16,25,

implode(', ',squareArray([1, 2, 3, 4, 5]))
#=> 1, 4, 9, 16, 25

Generator を使うと以下のように書けます。
(関数の返り値が Generator であることを意識する必要はあるので、ちょっと考えものですが🤔)

function squareGenerator(array $arr): \Generator
{
    foreach ($arr as $v) {
        yield $v ** 2;
    }
}

foreach (squareGenerator([1, 2, 3, 4, 5]) as $squared) {
    echo $squared . ',';
}
#=> 1,4,9,16,25,

implode(', ', iterator_to_array(squareGenerator([1, 2, 3, 4, 5])))
#=> 1, 4, 9, 16, 25

テストしたいコード

今回は、 jolicode/slack-php-api を用いて以下のような class SlackClient を書いたとします。
$this->client->iterateUsersList()iterable を返すので、その場合に PHPUnit でどのように書けばよいの??ってなりました。

<?php
declare(strict_types=1);

namespace App\ExternalService;

use JoliCode\Slack\Api\Model\ObjsUser;
use JoliCode\Slack\Api\Runtime\Client\Client;
use JoliCode\Slack\ClientFactory;

class SlackClient
{

    /**
     * @var \Jolicode\Slack\Api\Runtime\Client\Client Slackクライアント
     */
    private Client $client;

    /**
     * @param \Jolicode\Slack\Api\Runtime\Client\Client|null $client Slackクライアント
     */
    public function __construct(Client $client = null)
    {
        $this->client = $client ?? ClientFactory::create(env('SLACK_TOKEN'));
    }

    /**
     * Slackの表示名からSlackユーザーを取得する
     *
     * @param string $screenName 表示名
     * @return \JoliCode\Slack\Api\Model\ObjsUser Slack側のユーザー情報
     */
    public function getUserByScreenName(string $screenName): ?ObjsUser
    {
        foreach ($this->client->iterateUsersList() as $user) {
            $displayName = $user->getProfile()->getDisplayName();
            if ($displayName === $screenName) {
                return $user;
            }
        }

        return null;
    }
}

結論

以下のようなかんじに書くとよいです。

/**
 * getUserByScreenName()のテスト
 *
 * @return void
 */
public function test_getUserByScreenName(): void
{
    $screenName = 'dummy';
    $expected = new ObjsUser();
    $expected->setProfile((new ObjsUserProfile())->setDisplayName($screenName));
 
    $clientMock = $this->createPartialMock(Client::class, 'iterateUsersList');
    $clientMock
        ->expects($this->atLeastOnce())
        ->method('iterateUsersList')
        ->will($this->returnCallback(function () use ($expected) {
            foreach ([$expected] as $user) {
                yield $user;
            }
        }));

    $actual = (new SlackClient($clientMock))->getUserByScreenName($screenName);
    $this->assertSame($expected, $actual);
}

PHPUnit の $this->returnCallback() を用いて yield $user として都度 $user を返す closure を Mock に突っ込んであげればよいかんじです💪

おわりに

ナウいかんじの PHP を書くとしたらどう書けばいいんだ〜〜〜?ってたまに迷うことがあったので、 tips として書き起こしてみました。
もし困った時にこのへんが参考になれば、Google先生最高!インターネット幸せ!ってなるのでよきだな〜〜と思っています🙌

明日は @naopusyu さんのエントリです!おたのしみに〜〜👋

[PR] 最後に宣伝

コネヒト株式会社 では ママリ を中心に「あなたの家族像が実現できる社会をつくる」ためのプロダクトを鋭意開発しております!
サーバーサイドは CakePHP とか Go で書いていることが多いのですが、このあたりをぼくらと一緒にバリバリ進めてくれるエンジニアを大募集しています〜〜🙏

興味のある方がいらっしゃれば、以下からご応募いただくか、 Twitter で @takoba_ までご連絡いただけますと幸いです🤝

また、コネヒト社のメンバーが執筆している コネヒト Advent Calendar 2021 もぜひチェックしてくれよな!🙏

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?