Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test output to php://stdout with PHPUnit

I am using Symfony\Component\Console\Output\ConsoleOutput to write to the console.

Explicitly, I am writing to php://stdout.

In my unit tests, I would like to be able to check the output to the console.

Using the PHPUnit method expectOutputString(), I can check for output:

// Passes, as expected

public function testOutputBufferingEcho()
{
    $this->expectOutputString('Hello');

    echo 'Hello';
}

This works with output to php://output too:

// Passes, as expected

public function testOutputBufferingOutput()
{
    $this->expectOutputString('Hello');

    $out = fopen('php://output', 'w');
    fputs ($out, 'Hello');
    fclose($out);
}

However, it does not work with output to php://stdout (the one ConsoleOutput uses as default):

// Failed asserting that two strings are equal.
// --- Expected
// +++ Actual
// @@ @@
// -'Hello'
// +''

public function testOutputBufferingStdOut()
{
    $this->expectOutputString('Hello');

    $out = fopen('php://stdout', 'w');
    fputs ($out, 'Hello');
    fclose($out);
}

Additionally, it appears it is not possible to use the ob_* functions to capture output directly to php://stdout.

Is there anyway to test output to php://stdout with PHPUnit?

Or is there any other way to capture the output to php://stdout into a string (and so test in PHPUnit)?

The above tests ran in PHPUnit 5.5.5.

Thank you in advance.

like image 273
Jonathan Maron Avatar asked Sep 30 '16 04:09

Jonathan Maron


2 Answers

There is a way to replace STDOUT with any other resource: Close it. The next opened resource will have the file descriptor "1" (STDOUT) because this is the first free one.

fclose(STDOUT);
$fakestdout = fopen('php://memory', 'r+');

Now any output goes to $fakestdout and you can read from it in your test case.

The only problem is that this operation cannot be reverted. So from now on every attempt to write to STDOUT (including "echo") will go to $fakestdout, or nowhere, after you close it. You cannot reopen STDOUT once closed.

But if you run PHPUnit with the --stderr argument to use STDERR for the PHPUnit output, this should work.

like image 124
Fabian Schmengler Avatar answered Nov 16 '22 07:11

Fabian Schmengler


A quick and dirty way to capture php://stdout (or any other stream) would be to use a quicker and dirtier stream filter.

Example:

class Intercept extends php_user_filter
{
    public static $cache = '';
    public function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            self::$cache .= $bucket->data;
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

stream_filter_register("intercept", "Intercept");

$stdout = fopen('php://stdout', 'w'); // or $yerSymfonyThingy->getStream()
stream_filter_append($stdout, "intercept");

fwrite($stdout, "Hello\n");
var_dump(Intercept::$cache);

Output:

Hello
string(6) "Hello
"

Everything written to the stream gets collected in Intercept::$cache for your perusal.

You can prevent normal output of the stream as well by replacing PSFS_PASS_ON with PSFS_FEED_ME if you like.

like image 13
user3942918 Avatar answered Nov 16 '22 08:11

user3942918