Yami Odymel
Yami Odymel

Reputation: 1898

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

Upvotes: 29

Views: 10631

Answers (5)

fearis
fearis

Reputation: 499

Use a Zend\Diactoros\Stream

https://zendframework.github.io/zend-diactoros/usage/

$_POST['foo'] = 'bar';
use Zend\Diactoros\ServerRequestFactory;
$psrRequest = ServerRequestFactory::fromGlobals();
var_dump($psrRequest->getParsedBody()); // foo => bar
var_dump($_POST); // foo => bar

more info https://laracasts.com/discuss/channels/general-discussion/psr-7?page=1

Upvotes: 1

Jails
Jails

Reputation: 410

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

    });

});

Upvotes: 1

AD7six
AD7six

Reputation: 66238

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.

Upvotes: 12

geocar
geocar

Reputation: 9305

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).

Upvotes: 2

Finwe
Finwe

Reputation: 6745

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.

Upvotes: 9

Related Questions