PHPUnit测试file_get_contents

Amo*_*Amo 8 testing phpunit unit-testing mocking

我有一个类,它有一个利用PHP的全局file_get_contents函数的方法.我需要在类上测试方法,而不是实际调用全局函数.

我知道我可以使用命名空间来覆盖返回的内容file_get_contents,但是我的测试已经在一个单独的命名空间中,所以我不能简单地将命名空间与类匹配.

这是一些代码:

班级

<?php
namespace MyVendor\MyProject;

class MyClass
{

private $someProperty;

public function __construct($override = '')
{
    $this->someProperty = $override;
}

public function myMethod()
{
    $request = 'http://example.com';
    $response = $this->someMethodUsingGlobals($request);
    // Do something with the response..
}
public function someMethodUsingGlobals($url)
{
    return json_decode(file_get_contents($url),true)['results'][0];
}

}
Run Code Online (Sandbox Code Playgroud)

考试

<?php
namespace MyProjectTests;

public function test_it_does_something_with_the_response()
{
    $sut = new MyClass();

    $response = $sut->myMethod();

    $this->assertEquals('Some expectation', $response);
}
Run Code Online (Sandbox Code Playgroud)

我需要someMethodUsingGlobals()在类上模拟该方法,但不完全确定如何去做.

Xav*_*ero 5

解决方案:围绕本机函数进行类包装

最简单,最干净的方法是围绕本机函数创建包装器类。

如果遵循DDD和/或六边形体系结构,则可能会将其放置在“适配器”空间中,并且如果不遵循DDD或十六进制拱形结构,则将其放置在“接触外部世界”的任何类组的旁边。

此包装器是一类类:

<?php

declare( strict_types = 1 );

namespace MyVendor\MyProject\Adapters;

class FileGetContentsWrapper
{
    public function fileGetContents( string $filename )
    {
        return file_get_contents( $filename );
    }
}
Run Code Online (Sandbox Code Playgroud)

该类仅使用本机函数,因此无法进行测试。

但是有了它,您只需将“ unesting”“转移”到这种单行代码类,现在您就可以使用其他所有file_get_contents()可测试的地方围绕逻辑(除了文件读取之外)覆盖代码。

您原来的课程,已修改

您可以这样进行:

  1. 您通过构造函数注入服务。
  2. 您将包装器视为服务。
  3. 如果您在最新版本中使用诸如symfony之类的框架,则可以使用自动装配进行注入,以简化类的构造。

例如,您的课程可能会导致以下结果:

<?php

namespace MyVendor\MyProject;

use MyVendor\MyProject\Adapters\FileGetContentsWrapper;

class MyClass
{
    private $fileGetContentsWrapper;

    public function __construct( FileGetContentsWrapper $fileGetContentsWrapper )
    {
        $this->fileGetContentsWrapper = $fileGetContentsWrapper;
    }

    /* ... */

    public function someMethodUsingTheWrapper( $url )
    {
        $contents = $this->fileGetContents( $url );
        return json_decode( $contents, true )[ 'results' ][ 0 ];
    }
}
Run Code Online (Sandbox Code Playgroud)

如何测试

您执行以下操作:

  1. 在测试类中,为包装程序设置一个模拟程序。
  2. 在测试方法中,您可以配置模拟返回所需的任何内容。
  3. 您通常会调用您的课程。

例如:

<?php

declare( strict_types = 1 );

namespace MyProjectTests;

use MyVendor\MyProject\Adapters\FileGetContentsWrapper;
use MyVendor\MyProject\MyClass;
use PHPUnit\Framework\TestCase;

class MyClassTest extends TestCase
{
    private $fileGetContentsWrapper;

    //---------------------------------------------------------------------//
    // Setup                                                               //
    //---------------------------------------------------------------------//

    protected function setUp()
    {
        $this->fileGetContentsWrapper = $this->createMock( FileGetContentsWrapper::class )

        parent::setUp();
    }

    //---------------------------------------------------------------------//
    // Tests                                                               //
    //---------------------------------------------------------------------//

    public function testSomeMethodUsingTheWrapper()
    {
        $sut = $this->getSut();

        $someSimulatedJson = '{"results":["abc","xyz"]}';
        $this->fileGetContentsWrapper->method( 'fileGetContents' )->willReturn( $someSimulatedJson );

        $this->assertEquals( 'xyz', $sut->someMethodUsingGlobals( 'dummy-url' ) );
    }

    //---------------------------------------------------------------------//
    // Private                                                             //
    //---------------------------------------------------------------------//

    private function getSut() : MyClass
    {
        return new MyClass( $this->fileGetContentsWrapper );
    }
}
Run Code Online (Sandbox Code Playgroud)

就这样!希望对您有所帮助!


Mat*_*teo 3

您可以使用部分模拟对象对其进行归档:您可以仅模拟类的特定方法并执行(和测试)其他方法。

进一步参考这里

例如,假设您修改后的类:

<?php
namespace Acme\DemoBundle\Service;


class MyClass {

    public function myMethod()
    {
        $request = 'http://domain.com';
        $response = $this->someMethodUsingGlobals($request);
        // Do something with the response..

        return $response;
    }
    public function someMethodUsingGlobals($url)
    {
        return json_decode(file_get_contents($url),true)['results'][0];
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用以下测试类进行测试:

<?php
namespace Acme\DemoBundle\Tests;



class MyProjectTest extends \PHPUnit_Framework_TestCase
{

    public function test_it_does_something_with_the_response()
    {
        $sut = $this->getMock('Acme\DemoBundle\Service\MyClass', array('someMethodUsingGlobals') );

        // Set up the expectation for the someMethodUsingGlobals() method
        // to be called only once and with the string 'http://domain.com'
        // as its parameter.
        $sut->expects($this->once())
            ->method('someMethodUsingGlobals')
            ->with($this->equalTo('http://domain.com'))
            ->willReturn('Some expectation');

        $response = $sut->myMethod();


        $this->assertEquals('Some expectation', $response);
    }
}
Run Code Online (Sandbox Code Playgroud)

因此该方法 someMethodUsingGlobals不会执行并返回模拟定义中定义的值。该方法myMethod使用模拟函数执行和处理。

希望这有帮助