尝试使用VFSStream测试文件系统操作



我正在尝试模拟一个文件系统操作(实际上是从php://input)使用vfsStream,但缺乏像样的文档和示例确实阻碍了我

我正在测试的类的相关代码如下:

class RequestBody implements ifacerequestRequestBody
{
    const
        REQ_PATH    = 'php://input',
    protected
        $requestHandle  = false;
    /**
     * Obtain a handle to the request body
     * 
     * @return resource a file pointer resource on success, or <b>FALSE</b> on error.
     */
    protected function getHandle ()
    {
        if (empty ($this -> requestHandle))
        {
            $this -> requestHandle  = fopen (static::REQ_PATH, 'rb');
        }
        return $this -> requestHandle;
    }
}

我在PHPUnit测试中使用的设置如下:

protected function configureMock ()
{
    $mock   = $this -> getMockBuilder ('gordianreefknothttprequestRequestBody');
    $mock   -> setConstructorArgs (array ($this -> getMock ('gordianreefknothttpifaceRequest')))
            -> setMethods (array ('getHandle'));

    return $mock;
}
/**
 * Sets up the fixture, for example, opens a network connection.
 * This method is called before a test is executed.
 */
protected function setUp ()
{
    vfsStreamWrapper::register();
    vfsStream::setup ('testReqBody');
    $mock   = $this -> configureMock ();
    $this -> object = $mock -> getMock ();
    $this -> object -> expects ($this -> any ())
                    -> method ('getHandle')
                    -> will ($this -> returnCallback (function () {
                        return fopen ('vfs://testReqBody/data', 'rb');
                    }));
}

在一个实际的测试中(它调用一个间接触发getHandle()的方法),我尝试设置VFS并运行如下断言:

public function testBodyParsedParsedTrue ()
{
    // Set up virtual data
    $fh     = fopen ('vfs://testReqBody/data', 'w');
    fwrite ($fh, 'test write 42');
    fclose ($fh);
    // Make assertion
    $this -> object -> methodThatTriggersGetHandle ();
    $this -> assertTrue ($this -> object -> methodToBeTested ());
}

这只会导致测试挂起。

很明显,我在这里做了一些非常错误的事情,但考虑到文档的状态,我无法确定我应该做什么。这是vfsstream引起的,还是phpunit在嘲笑我需要在这里看到的东西?

所以。。。如何使用流进行测试?vfsStream所做的只是为文件系统操作提供一个自定义流包装器。您不需要完整的vfsStream库来模拟单个流参数的行为——这不是正确的解决方案。相反,您需要编写并注册自己的一次性流包装器,因为您不想模拟文件系统操作。

假设你有以下简单的类要测试:

class ClassThatNeedsStream {
    private $bodyStream;
    public function __construct($bodyStream) {
        $this->bodyStream = $bodyStream;
    }
    public function doSomethingWithStream() {
        return stream_get_contents($this->bodyStream);
    }
}

在现实生活中:

$phpInput = fopen('php://input', 'r');
new ClassThatNeedsStream($phpInput);

因此,为了测试它,我们创建了自己的流包装器,它将允许我们控制传入流的行为。我不能说太多细节,因为自定义流包装器是一个大主题但是基本上过程是这样的:

  1. 创建自定义流包装
  2. 用PHP注册该流包装器
  3. 使用注册的流包装方案打开资源流

所以你的自定义流看起来像:

class TestingStreamStub {
    public $context;
    public static $position = 0;
    public static $body = '';
    public function stream_open($path, $mode, $options, &$opened_path) {
        return true;
    }
    public function stream_read($bytes) {
        $chunk = substr(static::$body, static::$position, $bytes);
        static::$position += strlen($chunk);
        return $chunk;
    }
    public function stream_write($data) {
        return strlen($data);
    }
    public function stream_eof() {
        return static::$position >= strlen(static::$body);
    }
    public function stream_tell() {
        return static::$position;
    }
    public function stream_close() {
        return null;
    }
}

然后在你的测试用例中,你会这样做:

public function testSomething() {
    stream_wrapper_register('streamTest', 'TestingStreamStub');
    TestingStreamStub::$body = 'my custom stream contents';
    $stubStream = fopen('streamTest://whatever', 'r+');
    $myClass = new ClassThatNeedsStream($stubStream);
    $this->assertEquals(
        'my custom stream contents',
        $myClass->doSomethingWithStream()
    );
    stream_wrapper_unregister('streamTest');
}

然后,您可以简单地更改我在流包装器中定义的静态属性,以更改读取流后返回的数据。或者,扩展基本流包装器类并注册它,为测试提供不同的场景。

这是一个非常基本的介绍,但重点是:除非你在嘲笑实际的文件系统操作,否则不要使用vfsStream——这就是它的设计目的。否则,编写一个自定义流包装器进行测试。

PHP提供了一个原型流包装器类来帮助您入门:http://www.php.net/manual/en/class.streamwrapper.php

我一直在努力寻找类似的答案——我发现文档也缺乏。

我怀疑您的问题是vfs://testReqBody/data不是现有文件的路径,(因为php://input永远都是。)

如果接受的答案是可接受的答案,那么这与vfsStreamWrapper是等效的。

<?php
// ...
$reqBody = "Request body contents"
vfsStream::setup('testReqBody', null, ['data' => $reqBody]);
$this->assertSame($reqBody, file_get_contents('vfs://testReqBody/data'));

或者,如果需要将其拆分,以便在调用vfsStream::setup()之后定义内容,方法如下。

<?php
//...
$reqBody = "Request body contents"
$vfsContainer = vfsStream::setup('testReqBody');
vfsStream::newFile('data')->at($vfsContainer)->setContent($reqBody);
$this->assertSame($reqBody, file_get_contents('vfs://testReqBody/data'));

从原始代码中需要注意的另一件事是,使用vfsStream::setup() 时不需要调用vfsStreamWrapper::register();

最新更新