需要测试在Laravel 5.1中使用CURL的服务

时间:2015-12-10 04:40:34

标签: php phpunit laravel-5.1

我为搜索YouTube的Laravel 5.1 API构建了一项服务。我正在尝试为它编写测试,但我很难弄清楚如何模拟功能。以下是该服务。

class Youtube
{
/**
 * Youtube API Key
 *
 * @var string
 */
protected $apiKey;

/**
 * Youtube constructor.
 *
 * @param $apiKey
 */
public function __construct($apiKey)
{
    $this->apiKey = $apiKey;
}

/**
 * Perform YouTube video search.
 *
 * @param $channel
 * @param $query
 * @return mixed
 */
public function searchYoutube($channel, $query)
{
    $url = 'https://www.googleapis.com/youtube/v3/search?order=date' .
        '&part=snippet' .
        '&channelId=' . urlencode($channel) .
        '&type=video' .
        '&maxResults=25' .
        '&key=' . urlencode($this->apiKey) .
        '&q=' . urlencode($query);
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $result = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($result, true);

    if ( is_array($result) && count($result) ) {
        return $this->extractVideo($result);
    }
    return $result;
}

/**
 * Extract the information we want from the YouTube search resutls.
 * @param $params
 * @return array
 */
protected function extractVideo($params)
{
    /*
    // If successful, YouTube search returns a response body with the following structure:
    //
    //{
    //  "kind": "youtube#searchListResponse",
    //  "etag": etag,
    //  "nextPageToken": string,
    //  "prevPageToken": string,
    //  "pageInfo": {
    //    "totalResults": integer,
    //    "resultsPerPage": integer
    //  },
    //  "items": [
    //    {
    //        "kind": "youtube#searchResult",
    //        "etag": etag,
    //        "id": {
    //            "kind": string,
    //            "videoId": string,
    //            "channelId": string,
    //            "playlistId": string
    //        },
    //        "snippet": {
    //            "publishedAt": datetime,
    //            "channelId": string,
    //            "title": string,
    //            "description": string,
    //            "thumbnails": {
    //                (key): {
    //                    "url": string,
    //                    "width": unsigned integer,
    //                    "height": unsigned integer
    //                }
    //            },
    //        "channelTitle": string,
    //        "liveBroadcastContent": string
    //      }
    //  ]
    //}
     */
    $results = [];
    $items = $params['items'];

    foreach ($items as $item) {

        $videoId = $items['id']['videoId'];
        $title = $items['snippet']['title'];
        $description = $items['snippet']['description'];
        $thumbnail = $items['snippet']['thumbnails']['default']['url'];

        $results[] = [
            'videoId' => $videoId,
            'title' => $title,
            'description' => $description,
            'thumbnail' => $thumbnail
        ];
    }

    // Return result from YouTube API
    return ['items' => $results];
}
}

我创建了此服务以从控制器中抽象出功能。然后我用Mockery来测试控制器。现在我需要弄清楚如何测试上面的服务。任何帮助表示赞赏。

2 个答案:

答案 0 :(得分:3)

需要说明的是,由于采用了硬编码的curl_*方法,您的课程不适用于隔离单元测试。为了让它更好,你至少有两个选择:

1)将class CurlCaller { public function call($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); return $result; } } class Youtube { public function __construct($apiKey, CurlCaller $caller) { $this->apiKey = $apiKey; $this->caller = $caller; } } 函数调用解压缩到另一个类,并将该类作为参数传递

curl_*

现在您可以轻松地模拟CurlCaller类。 有很多现成的解决方案可以抽象网络。例如,Guzzle很棒

2)另一种选择是提取// Firstly change your class: class Youtube { // ... public function searchYoutube($channel, $query) { $url = 'https://www.googleapis.com/youtube/v3/search?order=date' . '&part=snippet' . '&channelId=' . urlencode($channel) . '&type=video' . '&maxResults=25' . '&key=' . urlencode($this->apiKey) . '&q=' . urlencode($query); $result = $this->callUrl($url); $result = json_decode($result, true); if ( is_array($result) && count($result) ) { return $this->extractVideo($result); } return $result; } // This method will be overriden in test. protected function callUrl($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); return $result; } } 对受保护方法的调用并模拟该方法。这是一个有效的例子:

callUrl

现在你可以模拟方法fixtures/youtube-response-stub.json。但首先,我们将预期的api响应放到class YoutubeTest extends PHPUnit_Framework_TestCase { public function testYoutube() { $apiKey = 'StubApiKey'; // Here we create instance of Youtube class and tell phpunit that we want to override method 'callUrl' $youtube = $this->getMockBuilder(Youtube::class) ->setMethods(['callUrl']) ->setConstructorArgs([$apiKey]) ->getMock(); // This is what we expect from youtube api but get from file $fakeResponse = $this->getResponseStub(); // Here we tell phpunit how to override method and our expectations about calling it $youtube->expects($this->once()) ->method('callUrl') ->willReturn($fakeResponse); // Get results $list = $youtube->searchYoutube('UCSZ3kvee8aHyGkMtShH6lmw', 'php'); $expected = ['items' => [[ 'videoId' => 'video-id-stub', 'title' => 'title-stub', 'description' => 'description-stub', 'thumbnail' => 'https://i.ytimg.com/vi/stub/thimbnail-stub.jpg', ]]]; // Finally assert result with what we expect $this->assertEquals($expected, $list); } public function getResponseStub() { $response = file_get_contents(__DIR__ . '/fixtures/youtube-response-stub.json'); return $response; } } 文件中。

extractVideo

运行测试并且... OMG FAILURE !! 1您在$item方法中存在拼写错误,应该是$items而不是$videoId = $item['id']['videoId']; $title = $item['snippet']['title']; $description = $item['snippet']['description']; $thumbnail = $item['snippet']['thumbnails']['default']['url']; 。让我们解决它

{{1}}

好的,现在通过了。

如果您想通过调用Youtube API来测试您的课程,您只需要创建正常的Youtube课程。

顺便说一下,有php-youtube-api lib,它有laravel 4和laravel 5的提供者,也有测试

答案 1 :(得分:0)

如果更改进行CURL调用的代码不是一个选项,它仍然可以完成,但它并不漂亮。

此解决方案假定进行CURL调用的代码将其目标URL基于环境变量。这里的要点是,您可以将呼叫重定向回您自己的应用程序,再到可以通过测试控制输出的端点。由于正在执行测试的app实例实际上与CURL调用掉头时访问的实例不同,因此我们处理范围问题以允许测试控制输出的方式是{{1缓存,它将您的虚拟数据记录到在运行时访问的外部文件。

  1. 在测试中,使用以下命令更改负责CURL调用域的环境变量的值: forever
  2. 由于putenv("SOME_BASE_URI=".config('app.url')."/curltest/")通常会将默认phpunit.xml设置为CACHE_DRIVER,而是永久性的,因此您必须在测试中将其设置为更改它回到array

    file
    1. config(['cache.default' => 'file']); 文件夹中创建一个新类,当请求符合一组可配置的条件时,它将负责返回给定的响应:

      使用Illuminate \ Http \ Request;

      类ResponseFactory {

      tests

      }

    2. 由于它位于public function getResponse(Request $request) { $request = [ 'method' => $request->method(), 'url' => parse_url($request->fullUrl()), 'parameters' => $request->route()->parameters(), 'input' => $request->all(), 'files' => $request->files ]; $responses = app('cache')->pull('test-response', null); $response = collect($responses)->filter(function (array $response) use ($request) { $passes = true; $response = array_dot($response); $request = array_dot($request); foreach ($response as $part => $rule) { if ($part == 'response') { continue; } $passes &= is_callable($rule) ? $rule($request[$part]) : ($request[$part] == $rule); } return $passes; })->pluck('response')->first() ?: $request; if (is_callable($response)) { $response = $response($request); } return response($response); } /** * This uses permanent cache so it can persist between the instance of this app from which the test is being * executed, to the instance being accessed by a CURL call * * @param array $responses */ public function setResponse(array $responses) { app('cache')->forever('test-response', $responses); } 文件夹中,而不在tests命名空间下,因此请务必将其添加到App文件的auto-load.classmap部分,然后运行{ {1}}在命令行上。 此外,这是使用自定义帮助函数:

      composer.json
      1. 在路由中添加一些仅测试端点。 (可悲的是,放置 composer dumpautoload;composer install 据我所知,在你的测试中是行不通的。) if (!function_exists('parse_url')) { /** * @param $url * @return array */ function parse_url($url) { $parts = parse_url($url); if (array_key_exists('query', $parts)) { $query = []; parse_str(urldecode($parts['query']), $query); $parts['query'] = $query; } return $parts; } } 如果需要,您甚至可以将其包装在$this->app->make(Router::class)->match($method, $endpoint, $closure);块中,这样可以确保 Route::post('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::get('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::put('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::patch('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); Route::delete('curltest/{endpoint?}', function (Illuminate\Http\Request $request) { return app(ResponseFactory::class)->getResponse($request); }); 。{/ p>

      2. 配置响应的内容以反映假定提示特定if值的端点。在测试中放置这样的东西。 config('app.debug') == true