Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way test STDERR output in PHPUnit?

Tags:

php

phpunit

I have a class that outputs to STDERR, but I am having trouble finding a way to get PHPUnit to test its output.

The class, PHPUnit_Extensions_OutputTestCase, also did not work.

like image 437
Kevin Herrera Avatar asked Dec 01 '11 22:12

Kevin Herrera


3 Answers

I don't see a way to buffer stderr as you can with stdout, so I would refactor your class to move the call that does the actual output to a new method. This will allow you to mock that method during testing to validate the output or subclass with one that buffers.

For example, say you have a class that lists files in a directory.

class DirLister {
    public function list($path) {
        foreach (scandir($path) as $file) {
            echo $file . "\n";
        }
    }
}

First, extract the call to echo. Make it protected so you can override and/or mock it.

class DirLister {
    public function list($path) {
        foreach (scandir($path) as $file) {
            $this->output($file . "\n");
        }
    }

    protected function output($text) {
        echo $text ;
    }
}

Second, either mock or subclass it in your test. Mocking is easy if you have a simple test or don't expect many calls to output. Subclassing to buffer the output is easier if you have a large amount of output to verify.

class DirListTest extends PHPUnit_Framework_TestCase {
    public function testHomeDir() {
        $list = $this->getMock('DirList', array('output'));
        $list->expects($this->at(0))->method('output')->with("a\n");
        $list->expects($this->at(1))->method('output')->with("b\n");
        $list->expects($this->at(2))->method('output')->with("c\n");
        $list->list('_files/DirList'); // contains files 'a', 'b', and 'c'
    }
}

Overriding output to buffer all $text into an internal buffer is left as an exercise for the reader.

like image 189
David Harkness Avatar answered Oct 11 '22 03:10

David Harkness


You can't intercept and fwrite(STDERR); from within a test case with the help of phpunit. For that matter you can't even intercept fwrite(STDOUT);, not even with output buffering.

Since i assume you don't really want to inject STDERR into your "errorOutputWriter" (as it doesn't make any sense for the class to write somewhere else) this is one of the very few cases where I'd suggest that sort of small hack:

<?php 

class errorStreamWriterTest extends PHPUnit_Framework_TestCase {

    public function setUp() {
        $this->writer = new errorStreamWriter();
        $streamProp = new ReflectionProperty($this->writer, 'errorStream');
        $this->stream = fopen('php://memory', 'rw');
        $streamProp->setAccessible(true);
        $streamProp->setValue($this->writer, $this->stream);
    }

    public function testLog() {
        $this->writer->log("myMessage");
        fseek($this->stream, 0);
        $this->assertSame(
            "Error: myMessage",
            stream_get_contents($this->stream)
        );
    }

}

/* Original writer*/
class errorStreamWriter {
    public function log($message) {
        fwrite(STDERR, "Error: $message");
    }
}

// New writer:
class errorStreamWriter {

    protected $errorStream = STDERR;

    public function log($message) {
        fwrite($this->errorStream, "Error: $message");
    }

}

It takes out the stderr stream and replaces it with an in memory stream and read that one back in the testcase to see if the right output was written.

Normally I'd for sure say "Inject the file path in the class" but with STDERR that doesn't make any sense to me so that would be my solution.

phpunit stderrTest.php 
PHPUnit @package_version@ by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 5.00Mb

OK (1 test, 1 assertion)

Update

After giving it some thought I'd say that not having somethink like an errorSteamWriter as a class might also work out.

Just having a StreamWriter and constructing it with new StreamWriter(STDERR); would result in a nicely testable class that can be reused for a lot of purposes in the application without hard coding some sort of "this is where errors go" into the class it's self and adding flexibility.

Just wanted to add this as an option to avoid the "ugly" test options :)

like image 24
edorian Avatar answered Oct 11 '22 04:10

edorian


Use a stream filter to capture/redirect the output to STDERR. The following example actually upercases and redirects to STDOUT but conveys the basic idea.

class RedirectFilter extends php_user_filter {
  static $redirect;

  function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
  $bucket->data = strtoupper($bucket->data);
  $consumed += $bucket->datalen;
  stream_bucket_append($out, $bucket);
  fwrite(STDOUT, $bucket->data);
}
return PSFS_PASS_ON;
  }
}

stream_filter_register("redirect", "RedirectFilter")
or die("Failed to register filter");

function start_redirect() {
  RedirectFilter::$redirect = stream_filter_prepend(STDERR, "redirect", STREAM_FILTER_WRITE);
}

function stop_redirect() {
  stream_filter_remove( RedirectFilter::$redirect);
}

start_redirect();
fwrite(STDERR, "test 1\n");
stop_redirect();
like image 41
rwb Avatar answered Oct 11 '22 03:10

rwb