Подтвердить что ты не робот

Как смоделировать php://ввод в PHP?

Я пишу unit test для моего проекта PHP,

unit test предназначен для моделирования данных php://input,

и я прочитал руководство, в нем говорится:

php://input - поток, доступный только для чтения, который позволяет вам читать исходные данные из органа запроса.

Как смоделировать php://input или написать тело запроса в моем PHP?


Здесь мой исходный код и unit test, оба упрощены.

Источник

class Koru
{
    static function build()
    {
        // This function will build an array from the php://input.
        parse_str(file_get_contents('php://input'), $input);

        return $input;
    }

    //...

Unit Test

function testBuildInput()
{
    // Trying to simulate the `php://input` data here.
    // NOTICE: THIS WON'T WORK.
    file_put_contents('php://input', 'test1=foobar&test2=helloWorld');

    $data = Koru::build();

    $this->assertEquals($data, ['test1' => 'foobar',
                                'test2' => 'helloWorld']);
}
4b9b3361

Ответ 1

Смотрите пакет vfsStream и этот вопрос и ответы.

В принципе, вы захотите параметризовать свою службу, которая считывает данные, чтобы принять путь:

public function __construct($path)
{
    $data = file_get_contents($path); // you might want to use another FS read function here
}

И затем, в тесте, укажите путь потока vfsStream:

\vfsStreamWrapper::register();
\vfsStream::setup('input');

$service = new Service('vfs://input') 

В вашем коде вы бы указали php://input как обычно.

Ответ 2

Используйте двойной тест

Учитывая код в вопросе, самым простым решением является реструктуризация кода:

class Koru
{
    static function build()
    {
        parse_str(static::getInputStream(), $input);
        return $input;
    }

    /**
     * Note: Prior to PHP 5.6, a stream opened with php://input could
     * only be read once;
     *
     * @see http://php.net/manual/en/wrappers.php.php
     */
    protected static function getInputStream()
    {
        return file_get_contents('php://input');
    }

И используйте двойной тест:

class KoruTestDouble extends Koru
{
    protected static $inputStream;

    public static function setInputStream($input = '')
    {
        static::$inputStream = $input;
    }

    protected static function getInputStream()
    {
        return static::$inputStream;
    }
}

Затем тестовый метод использует двойной тест, а не сам класс:

function testBuildInput()
{
    KoruTestDouble::setInputStream('test1=foobar&test2=helloWorld');

    $expected = ['test1' => 'foobar', 'test2' => 'helloWorld'];
    $result = KoruTestDouble::build();

    $this->assertSame($expected, $result, 'Stuff be different');
}

Избегайте статических классов, если возможно

Большинство трудностей с сценарием в вопросе вызваны использованием статических методов класса, статические классы делают тестирование сложным. Если это вообще возможно, избегайте использования статических классов и используйте методы экземпляра, которые позволяют решить такую ​​же проблему, используя mock objects.

Ответ 3

Такая экстремальная декомпозиция ничего не выигрывает и ведет очень хрупкий код. Ваши тесты должны выражать ожидания ваших интерфейсов, а не данные, которые вы им предоставили: действительно ли PHP не может возвращать ["test2"=>"helloWorld","test1"=>"foobar"] в какой-то будущей версии? Является ли ваш код сломанным, если это так? Как вы думаете, что вы тестируете?

Я думаю, что вы это слишком смущаете.

$a->doit должен принимать $input как аргумент и не вызывать Koru::build как часть его инициализации. Затем вы можете протестировать $a->doit вместо тестирования parse_str.

Если вы настаиваете на нажатии этого примера, то Koru::build необходимо принять аргумент 'php://input' - это часто называют инъекцией зависимостей, где вы сообщаете своим функциям все, что им нужно знать. Затем, когда вы хотите "протестировать" вещи, вы можете просто передать какой-нибудь другой файл (или, например, URL-адрес данных).

Ответ 4

С Kahlan вы можете обезвредить функцию file_get_contents прямо так:

use My\Name\Space\Koru;

describe("::build()", function() {

    it("parses data", function() {

        allow('file_put_contents')->toBeCalled()->andRun(function() {
            return 'test1=foobar&test2=helloWorld';
        });
        expect(Koru::build())->toBe([
            'test1' => 'foobar',
            'test2' => 'helloWorld'
        ]);

    });

});