How do I test the following code without hitting the database? (This is php + phpunit btw)
Much untestable. Very sad. Wow.
class YayAClass
{
public function foo()
{
// stuff
// a wild instantiation of an object appears!
$object = new Object(); // getFromDatabase returns 1
$effect = $object->getFromDatabase( 'parameter' ); // does stuff with $effect
return $effect + 1;
}
}
WARNING
This is a stopgap measure. Unfortunately, we do not live in a perfect world where all your dependencies are injected.
It typically is not okay to fake the class that you are currently testing. However, if you are testing legacy code for which you need a temporary fix and save the issue to come back to, you can try the following “solution”.
See this resource about mocking the class being tested.
An example use case is you may have an instantiation of an object that is not critical to the usage of that function. For instance, if the instantiation of this object lives in one logic block of an if-else statement.
Example:
if ( 'something' ) { // do something} else { // do something else
$object = new Object();}
Wrap it
Fine. You have been warned. Let’s wrap it! Check dis outttt:
Implementation
class YayAClass
{
public function foo()
{
// stuff // Call the class function that instantiates Object
$object = $this->_createObject(); // getFromDatabase returns 1
$effect = $object->getFromDatabase( 'parameter' ); // does stuff with $effect
return $effect + 1;
} // Think of this like a getter
protected function _createObject()
{
return new Object();
}
}
Testing
/**
* @test
*/
public function foo()
{
// Create Object stub
$object = $this->getMockBuilder( 'Object' )
->setMethods( [ 'getFromDatabase' ] )
->getMock(); // Configure Object stub
$object->expects( $this->any() )
->method( [ 'getFromDatabase' ] )
->with( 'parameter' )
->willReturn( 1 ); // Create YayAClass stub
$yayAClass = $this->getMockBuilder( 'YayAClass' )
->setMethods( [ '_createObject' ] )
->getMock(); // Configure YayAClass stub
$yayAClass->expects( $this->any() )
->method( '_createObject' )
->willReturn( $object ); // Call the foo function you want to test
$effect = $yayAClass->foo(); $this->assertEquals( 2, $effect );}
Yay! You now have a working tested version!
But what about this sadness about faking the class you are testing? What are the alternatives? Dependency injection to the rescue!
Dependency Injection
Implementation
When possible, refactor the code to this:
class YayAClass
{
// Oh hey look! You can inject Object here
public function foo( $object )
{
// stuff // getFromDatabase returns 1
$effect = $object->getFromDatabase( 'parameter' ); // does stuff with $effect
return $effect + 1;
}
}
Testing
Now testing is easy!
/**
* @test
*/
public function foo()
{
$object = $this->getMockBuilder( 'Object' )
->setMethods( 'getFromDatabase' )
->getMock(); $object->expects( $this->any() )
->method( 'getFromDatabase' )
->with( 'parameter' )
->willReturn( 1 ) $yayAClass = new YayAClass(); // Call the foo function with injected Object
$effect = $yayAClass->foo( $object ); $this->assertEquals( 2, $effect );
}
And look! You’re not mocking YayAClass. All is well with the world. ❤
(If you enjoyed this blog post. Please hit the recommend button below & share!)