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.
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.
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)
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 :)
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With