Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/276.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 需要测试在Laravel 5.1中使用CURL的服务_Php_Phpunit_Laravel 5.1 - Fatal编程技术网

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

Php 需要测试在Laravel 5.1中使用CURL的服务,php,phpunit,laravel-5.1,Php,Phpunit,Laravel 5.1,我为我的Laravel5.1API构建了一个搜索YouTube的服务。我正试图为它编写一个测试,但在弄清楚如何模拟功能方面遇到了困难。以下是服务 class Youtube { /** * Youtube API Key * * @var string */ protected $apiKey; /** * Youtube constructor. * * @param $apiKey */ public function __construct($apiKey) {

我为我的Laravel5.1API构建了一个搜索YouTube的服务。我正试图为它编写一个测试,但在弄清楚如何模拟功能方面遇到了困难。以下是服务

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];
}
}

我创建此服务是为了从控制器中抽象功能。然后我用mockry测试了控制器。现在我需要弄清楚如何测试上述服务。非常感谢您的帮助。

需要说明的是,您的类不是为独立的单元测试而设计的,因为使用了硬编码的
curl.*
方法。为了让它变得更好,您至少有两种选择:

1) 提取对另一个类的
curl.*
函数调用,并将该类作为参数传递

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;
    }
}
现在您可以轻松地模拟调用方类。 有很多现成的解决方案可以抽象网络。例如,它是伟大的

2) 另一个选项是提取对受保护方法的
curl.*
调用并模拟该方法。以下是一个工作示例:

// 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
。但首先,让我们将预期的api响应放入
fixtures/youtube response stub.json
文件

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;
    }
}
运行测试并。。。天哪,失败!!1您在
extractVideo
方法中有打字错误,应该是
$item
而不是
$items
。让我们来解决它

$videoId = $item['id']['videoId'];
$title = $item['snippet']['title'];
$description = $item['snippet']['description'];
$thumbnail = $item['snippet']['thumbnails']['default']['url'];
好的,现在它过去了


如果你想通过调用Youtube API来测试你的类,你只需要创建一个普通的Youtube类



顺便说一句,还有一个lib,它提供了laravel4和laravel5的提供者,还提供了测试

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

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

  • 在测试中,使用以下命令更改负责CURL调用域的环境变量的值:
    putenv(“SOME_BASE_URI=“.config('app.url')。”/curltest/”)
  • 由于
    phpunit.xml
    通常会将默认的
    CACHE\u驱动程序设置为
    array
    ,这不是永久的,因此您必须将其放在测试中才能将其更改回
    文件

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

    使用\Http\Request

    类响应系数{

    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
    命名空间下,请确保将其添加到
    composer.json
    文件的
    auto-load.classmap
    部分,然后运行
    composer-dumpautoload;在命令行上安装composer
    。 此外,这是使用自定义帮助器函数:

    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)->匹配($method,$endpoint,$closure);
    就我所知,在你的测试范围内是行不通的。)
    
    路由::post('curltest/{endpoint?}',函数(照亮\Http\Request$Request){
    返回应用程序(ResponseFactory::class)->getResponse($request);
    });
    路由::get('curltest/{endpoint?}',函数(照亮\Http\Request$Request){
    返回应用程序(ResponseFactory::class)->getResponse($request);
    });
    路由::put('curltest/{endpoint?}',函数(illumb\Http\Request$Request){
    返回应用程序(ResponseFactory::class)->getResponse($request);
    });
    Route::patch('curltest/{endpoint?}',函数(照亮\Http\Request$Request){
    返回应用程序(ResponseFactory::class)->getResponse($request);
    });
    路由::delete('curltest/{endpoint?}',函数(照亮\Http\Request$Request){
    返回应用程序(ResponseFactory::class)->getResponse($request);
    });
    
    如果需要,您甚至可以将其包装在
    if
    块中,以确保
    config('app.debug')==true

  • 配置响应的内容以反映假定提示特定
    响应的端点。在测试中放置类似的内容。
    
    
    应用程序(ResponseFactory::class)->setResponse([[
    'url.path'=>“/curltest/$curlTargetEndpont”,
    “响应”=>“成功”
    ]]);