Попытка проверить работу файловой системы с VFSStream

Я пытаюсь смоделировать операцию файловой системы (на самом деле чтение из php://input) с помощью vfsStream, но отсутствие достойной документации и примеров действительно мешает мне.

Соответствующий код из класса, который я тестирую, выглядит следующим образом:

class RequestBody implements iface\request\RequestBody
{
    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 ('\gordian\reefknot\http\request\RequestBody');

    $mock   -> setConstructorArgs (array ($this -> getMock ('\gordian\reefknot\http\iface\Request')))
            -> 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 издевается над тем, на что мне нужно обратить внимание?

2 ответа

Решение

Итак... как проверить с потоками? Все, что делает 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'));

Еще одна вещь, которую следует отметить из исходного кода, вам не нужно звонить vfsStreamWrapper::register(); когда используешь vfsStream::setup()

Другие вопросы по тегам