Given the following implementation:
class Foo {
public function helper() {
// Does something with external side effects, like updating
// a database or altering the file system.
}
public function bar() {
if ($this->helper() === FALSE) {
throw new Exception(/* ... */);
}
}
}
How would I unit test Foo::bar() without incurring the side effects of Foo::helper() during testing?
I know I can mock Foo and stub Foo::helper():
public function testGoodBar() {
$mock = $this->getMock('Foo', array('helper'));
$this->expects($this->once())
->method('helper')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->bar());
}
…but this leaves the test wide open for code changes that introduce other methods which may have side effects. Then if the test is run again without being updated, the test itself will have permanent side effects.
I could also mock Foo, such that all its methods get mocked and won’t produce side effects:
public function testGoodBar() {
$mock = $this->getMock('Foo');
$this->expects($this->once())
->method('helper')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->bar());
}
…but then even Foo::bar() gets mocked, which is bad, since that’s the method we want to test.
The only solution I could come up with is explicitly mocking all methods except the one under test:
public function testGoodBar() {
$mock = $this->getMock('Foo', array_diff(
get_class_methods('Foo'),
'bar'
));
$this->expects($this->once())
->method('helper')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->bar());
}
…but this seems kludgy, and I feel like I’m missing something obvious.
(Take the comments under the question into consideration for this answer.)
If you’re extending a class whose sole purpose is to have side effects, I’d expect all of the extension code to also produce side effects. Hence you’ll have to account for that in your tests and set up an environment in which you can test code with side effects (i.e.: get a memcached instance up and running for this test).
If you do not want this (understandably), it would be better to write your code as a wrapper around the side-effect class in a way that it is mockable. So, your
Foo::__constructaccepts an instance of or factory for the class which produces side effects, so you can mock it in your tests to test only your side-effect-free code.