我如何模拟php://input 在PHP中?

我如何模拟php://input 在PHP中?,php,unit-testing,Php,Unit Testing,我正在为我的PHP项目编写一个单元测试 单元测试是模拟php://input数据 我读了手册,上面写着: php://input 是一个只读流,允许您读取原始数据 来自请求主体 如何模拟php://input,还是用我的PHP编写请求正文 这是我的源代码和单元测试,它们都被简化了 来源: class Koru { static function build() { // This function will build an array from the php

我正在为我的PHP项目编写一个单元测试

单元测试是模拟
php://input
数据

我读了手册,上面写着:

php://input 是一个只读流,允许您读取原始数据 来自请求主体

如何模拟
php://input
,还是用我的PHP编写请求正文


这是我的源代码和单元测试,它们都被简化了

来源

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

    //...
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']);
}
单元测试

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

    //...
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']);
}
见和

基本上,您希望将读取数据以接受路径的服务参数化:

public function __construct($path)
{
    $data = file_get_contents($path); // you might want to use another FS read function here
}
然后,在测试中,提供vfstream流路径:

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

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

在您的代码中,您将提供
php://input
与往常一样。

这种极端的分解不会得到任何结果,并且会导致非常脆弱的代码。您的测试应该表达对接口的期望,而不是您提供给它们的数据:在未来的版本中,PHP真的不能自由返回
[“test2”=>“helloWorld”,“test1”=>“foobar”]
?如果你的代码被破坏了,它会被破坏吗?你认为你到底在测试什么

我觉得你把事情复杂化了

$a->doit
应该将
$input
作为参数,而不是调用
Koru::build
作为其初始化的一部分。然后您可以测试
$a->doit
而不是测试

如果您坚持按此示例,则
Koru::build
需要使用
参数php://input“
–这通常被称为依赖项注入,您可以告诉函数需要知道的一切。然后,当你想“测试”东西时,你可以简单地传入一些其他文件(例如a)。

使用双重测试 考虑到问题中的代码,最简单的解决方案是重新构造代码:

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');
}
尽可能避免使用静态类 问题中场景的大多数困难都是由使用静态类方法造成的,静态类使测试变得困难。如果可能,请避免使用静态类,并使用实例方法来解决相同类型的问题。

通过使用,您可以直接使用以下方式对
文件获取内容
函数进行猴补丁:

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'
        ]);

    });

});

使用Zend\Diactoros\Stream


更多信息

@CBroe-这需要为单元测试编写大量的框架。我认为这里的要点是,您不能对依赖于
php://input
直接执行。@CBroe我想编辑或创建
php://input
直接在我的单元测试中,并将其传递给我想要的函数为了测试而不是提出真正的请求,我更容易管理单元测试。
php://input
是全局状态,即。我建议您将文件的源代码传递给
build()
函数,以明确其依赖关系。我正在创建一个类,帮助我将数组转换为对象,例如,如果我有
$\u POST
,我可以使用
Koru
来构建它,就像
$data=Koru::build($\u POST)
,因此,我可以像
$data->username
一样使用它,而不是
$\u POST['username']
,我甚至可以添加一些帮助函数,这使我更方便地处理数据更多的代码(只需要编写更多的代码,调试更多的代码,阅读更多的代码)并不是让世界变得更好,而是用更少的时间做更多的事情。停止编写“helper函数”,直到你们都确定没有其他人为你们编写它(堆栈溢出可以提供帮助),并且你们不止一次需要它。
PHP真的不能在将来的版本中自由返回[“test2”=>“helloWorld”,“test1”=>“foobar”]?
这个答案的前提似乎有缺陷,这是永远不会发生的,但是如果是这样的话,代码就像你建议的那样,测试会通过,但代码仍然会失败。不。您编写测试不是为了练习代码行,而是为了验证接口,以便在更改代码时可以保留接口。此示例没有说明如何定义从
vfs://input
,并在
文件获取内容时出错('vfs://input“)
被调用。要定义内容,必须定义vfstream文件。在调用
vfsStream:setup()
之前,也不需要调用
vfsStreamWrapper::register()