I have an abstract class with concrete method. So I want to test those concrete methods.
Here is my abstract class:
abstract class File { private $debug_filename_pattern = 'DELETE_ME_%s.debug'; private $filename; private $filepath; abstract public function buildFilename(); public function __construct($debug = false) { $filename = $this->buildFilename(); if ($debug) { $filename = sprintf($this->debug_filename_pattern, $filename); } $this->filename = $filename; $this->buildFilepath(); } private function buildFilepath() { $this->filepath = ini_get('upload_tmp_dir') . DIRECTORY_SEPARATOR . $this->filename; } }
I read the section on testing abstract classes in phpunit documentation and I came up with that test:
final class FileTest extends \PHPUnit_Framework_TestCase { public function test() { $stub = $this->getMockForAbstractClass('MyBundle\File', [true]); $stub->expects($this->atLeastOnce()) ->method('buildFilename') ->withAnyParameters() ->will($this->returnValue('test.log')); $this->assertEquals('C:\xampp\tmp\DELETE_ME_test.log.debug', $stub->getFilePath()); } }
But it is not working. My assert always returns that it fails with this error message:
Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'C:\xampp\tmp\DELETE_ME_test.log.debug' +'C:\xampp\tmp\DELETE_ME_.debug'
I understand that my mock object is instantiated and then I add a mock for buildFilename
method. Making my test always fail.
Is there a way to mock my abstract method before instantiation? Should I refactor my abstract class instead?
I don't think that you are able to set up your mock the way that you want. The construct method is being called when you →getMock()
. Then you are trying to set expectations after the fact.
Generally, I find that when something becomes difficult to test as in this case it is a sign that there is an issue with the design. I think that the issue you have is that you are doing too much in your constructor in this case.
You are doing all sorts of heavy lifting to determine the filepath on the construct of your object. Why not change it so that it happens when you call getFilePath
. Your class would end up looking like this:
abstract class File { private $debug_filename_pattern = 'DELETE_ME_%s.debug'; private $filename; private $filepath; protected $debug; abstract public function buildFilename(); public function __construct($debug = false) { $this->debug = $debug; } private function buildFilepath() { $filename = $this->buildFilename(); if ($this->debug) { $filename = sprintf($this->debug_filename_pattern, $filename); } $this->filename = $filename; $this->filepath = ini_get('upload_tmp_dir') . DIRECTORY_SEPARATOR . $this->filename; } public function getFilePath() { if(!this->filepath) { $this->buildFilepath(); } return $this->filepath; } }
Now in your test to make sure that the path gets built only once just add your assertion one more time.
final class FileTest extends \PHPUnit_Framework_TestCase { public function test() { $stub = $this->getMockForAbstractClass('MyBundle\File', [true]); $stub->expects($this->once()) ->method('buildFilename') ->withAnyParameters() ->will($this->returnValue('test.log')); $this->assertEquals('C:\xampp\tmp\DELETE_ME_test.log.debug', $stub->getFilePath()); $this->assertEquals('C:\xampp\tmp\DELETE_ME_test.log.debug', $stub->getFilePath()); } }