PHPUnit 中 Mockery 的基本使用

finecho發表於2019-05-21

@這是小豪的第十四篇文章

最近在學習超哥的 《PHP 擴充套件包實戰教程 - 從入門到釋出》,寫到單元測試的時候發現了一個新東西 Mockery ,雖然有使用的範例,但是看的我還是暈暈的不曉得是個啥子意思,到底什麼是 Mockery ?究竟怎麼用呢?今天就來給大家簡單的分享一下。

準備

有時候我們需要對專案中用到第三方 API 的地方做一些測試,但往往這個時候就很為難,如果說直接發起請求的話一般是需要付費的或者減少了呼叫次數,再者說你電腦沒有網路,那豈不是搞不定了,哈哈,為了讓測試更加的優雅,Mockery 就應時而生了。

說的直觀一點,Mockery 就是用來模仿你要執行的程式碼。比如說某段程式碼中有部分是需要傳送網路請求,那麼你就可以利用 Morckery 建立一個模擬請求,並給定這個請求的返回值(權當它是能夠通過請求,並能獲取到返回值的),這樣子就發揮了 Mockery 的真正作用。

如果說確確實實要真實的資料測試,那就只有老老實實請求了。

小案例

好,說了這麼多了,我們來實戰一下,我們就拿超哥的 overtrue/weather 來看:

class Weather
{
    ...

    /**
     * @var string
     */
    protected $key;

    /**
     * Weather constructor.
     *
     * @param string $key
     */
    public function __construct($key)
    {
        $this->key = $key;
    }

    /**
     * @return \GuzzleHttp\Client
     */
    public function getHttpClient()
    {
        return new Client($this->guzzleOptions);
    }

    /**
     * @param string $city
     * @param string $type
     * @param string $format
     *
     * @return \Psr\Http\Message\ResponseInterface
     *
     * @throws \Overtrue\Weather\Exceptions\HttpException
     * @throws \Overtrue\Weather\Exceptions\InvalidArgumentException
     */
    public function getWeather($city, $type = 'base', $format = 'json')
    {
        $url = 'https://restapi.amap.com/v3/weather/weatherInfo';

        if (!\in_array(\strtolower($format), ['xml', 'json'])) {
            throw new InvalidArgumentException('Invalid response format: '.$format);
        }

        if (!\in_array(\strtolower($type), ['base', 'all'])) {
            throw new InvalidArgumentException('Invalid type value(base/all): '.$type);
        }

        $format = \strtolower($format);

        $type = \strtolower($type);

        $query = array_filter([
            'key' => $this->key,
            'city' => $city,
            'output' => $format,
            'extensions' => $type,
        ]);

        try {
            $response = $this->getHttpClient()->get($url, [
                'query' => $query,
            ])->getBody()->getContents();

            return 'json' === $format ? \json_decode($response, true) : $response;
        } catch (\Exception $e) {
            throw new HttpException($e->getMessage(), $e->getCode(), $e);
        }
    }
    ...
}

可以看到在 getWeather 方法中是存在網路請求的,如何去模擬這個 http client 呢,現在,我們來看看 Mockery 是如何實現的。

實現的程式碼其實很簡單,我們先直接來看,然後我再一一解釋:

class WeatherTest extends TestCase
{
    .
    .
    .
    public function testGetWeather()
    {
        // 建立模擬介面響應值。
        $response = new Response(200, [], '{"success": true}');

        // 建立模擬 http client。
        $client = \Mockery::mock(Client::class);

        // 指定將會產生的形為(在後續的測試中將會按下面的引數來呼叫)。
        $client->allows()->get('https://restapi.amap.com/v3/weather/weatherInfo', [
            'query' => [
                'key' => 'mock-key',
                'city' => '深圳',
                'output' => 'json',
                'extensions' => 'base',
            ]
        ])->andReturn($response);  

        // 將 `getHttpClient` 方法替換為上面建立的 http client 為返回值的模擬方法。
        $w = \Mockery::mock(Weather::class, ['mock-key'])->makePartial();
        $w->allows()->getHttpClient()->andReturn($client); // $client 為上面建立的模擬例項。

        // 然後呼叫 `getWeather` 方法,並斷言返回值為模擬的返回值。
        $this->assertSame(['success' => true], $w->getWeather('深圳'));
    }
    .
    .
    .

我們首先看到的是:建立 http client 模擬 =>\Mockery::mock(Client::class);,是的這裡就模擬了一個 http client ,哈哈,假的。既然模擬了,我們該怎麼用呢,我們再來看:

         // 指定將會產生的形為(在後續的測試中將會按下面的引數來呼叫)。
        $client->allows()->get('https://restapi.amap.com/v3/weather/weatherInfo', [
            'query' => [
                'key' => 'mock-key',
                'city' => '深圳',
                'output' => 'json',
                'extensions' => 'base',
            ]
        ])->andReturn($response);  

簡單的解讀一下上面的程式碼:利用模擬的 http client 通過 allows 方法,呼叫 http client 中的 get 方法並通過 andReturn 給定自己設定的返回值。什麼意思呢,就是說假定這個網路請求的結果通過且結果為 $response;

我們繼續往下看:

        // 將 `getHttpClient` 方法替換為上面建立的 http client 為返回值的模擬方法。
        $w = \Mockery::mock(Weather::class, ['mock-key'])->makePartial();
        $w->allows()->getHttpClient()->andReturn($client); // $client 為上面建立的模擬例項。

        // 然後呼叫 `getWeather` 方法,並斷言返回值為模擬的返回值。
        $this->assertSame(['success' => true], $w->getWeather('深圳'));

可以看到程式碼中模擬了一個 Weather 物件, mock-key 為注入的 key,因為是模擬的所以也不需要太在意真實性。可以看到後面追加了一個 makePartial 方法,這個方法是幹嘛用的呢,就是 只 mock 部分方法,什麼意思呢,就拿下面的程式碼來說,通過模擬物件呼叫了 getHttpClient 方法,並給定假定的返回值,這裡呼叫了 getHttpClient方法,就只 mock 了 getHttpClient 這個方法,就是這個意思,哈哈。

我們繼續往下看,因為已經模擬了網路請求的返回值,所以我們接下來通過 getWeather 獲取的返回值也就是上面的 $response

至此,一個簡單的 Mockery 模擬就結束拉,不曉得大家有木有清楚究竟如何去使用。

常用方法

  • shouldReceive():需要呼叫的方法,就拿上面的例子來說如果需要呼叫 Weather 中的 getHttpClient 方法,就可以通過shouldReceive('getHttpClient') 來實現,並不僅僅只有 allows() 能實現的。

  • once():只呼叫一次。

  • with():應該被傳入的引數。

  • andReturn():假定的返回值。

  • andThrow():丟擲異常。

結束語

看完上面的案例之後,大家心裡多多少少也對 Mockery 有個大致的瞭解了,這裡沒有深入的挖掘 Mockery 是如何實現的,後面有機會再來深究一下。先用起來,方便測試,哈哈。

看完超哥的擴充套件包教程之後,自己也嘗試著寫了一個擴充套件包 《快遞物流查詢-快遞查詢介面元件》,寫到這裡發現自己擴充套件包中的 Mockery 測試貌似寫的有點問題,待會就去修復了,哈哈。寫的很粗糙,但是也是自己跨出的第一步,哈哈,開心。要是有個小星星就更開心了,哈哈。

感謝

finecho # Lhao

相關文章