Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I simulate php://input in PHP?

I'm writing an unit test for my PHP project,

the unit test is to simulate a php://input data,

and I read the manual, it says:

php://input is a read-only stream that allows you to read raw data from the request body.

How do I simulate the php://input, or write the request body in my PHP?


Here's my source code and unit test, both are simplified.

Source:

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']);
}
like image 801
Yami Odymel Avatar asked Jul 05 '16 07:07

Yami Odymel


4 Answers

See vfsStream package and this SO question and answers.

Basically, you would want to parametrize your service that reads data to accept a path:

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

And then, in a test, provide an vfsStream stream path:

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

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

In your code you would provide php://input as per usual.

like image 110
Finwe Avatar answered Nov 13 '22 01:11

Finwe


Use a test double

Given the code in the question, the simplest solution is to restructure the code:

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

And use a test double:

class KoruTestDouble extends Koru
{
    protected static $inputStream;

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

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

The test method then uses the test double, not the class itself:

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

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

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

Avoid static classes if possible

Most of the difficulties with the scenario in the question are caused by the use of static class methods, static classes make testing hard. If at all possible avoid the use of static classes and use instance methods which allows solving the same sort of problem using mock objects.

like image 40
AD7six Avatar answered Nov 12 '22 23:11

AD7six


This sort of extreme decomposition gains nothing and leads very brittle code. Your tests should express the expectations of your interfaces, and not the data you've supplied them with: Is PHP truly not free to return ["test2"=>"helloWorld","test1"=>"foobar"] in some future version? Is your code broken if it does? What exactly do you think you are testing?

I think you're overcomplicating this.

$a->doit should take $input as an argument and not call Koru::build as part of its initialisation. Then you can test $a->doit instead of testing parse_str.

If you insist on pressing on this example, then Koru::build needs to take an argument of 'php://input' – this is often called dependency injection, where you tell your functions everything they need to know. Then, when you want to "test" things, you can simply pass in some other file (or e.g. a data url).

like image 2
geocar Avatar answered Nov 12 '22 23:11

geocar


With Kahlan you can monkey patch the file_get_contents function directly like so:

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

    });

});
like image 1
Jails Avatar answered Nov 12 '22 23:11

Jails