I have a Factory class that returns a writer strategy based on the extension of a given file:
public static function getWriterForFile($file)
{
// create file info object
$fileInfo = new \SplFileInfo($file);
// check that an extension is present
if ('' === $extension = $fileInfo->getExtension()) {
throw new \RuntimeException(
'No extension found in target file: ' . $file
);
}
// build a class name using the file extension
$className = 'MyNamespace\Writer\Strategy\\'
. ucfirst(strtolower($extension))
. 'Writer';
// attempt to get an instance of the class
if (!in_array($className, get_declared_classes())) {
throw new \RuntimeException(
'No writer could be found for the extension: ' . $extension
);
}
$instance = new $className();
$instance->setTargetFile($file);
return $instance;
}
All of my strategies implement the same interface, for example:
class CsvWriter implements WriterStrategyInterface
{
public function setTargetFile($file)
{
...
}
public function writeLine(array $data)
{
...
}
public function flush()
{
...
}
}
I want to be able to test this method using a dummy extension so that my tests are not dependant on any particular strategy existing. I tried creating a mock for the interface with a set classname, but this does not seem to have declared the mock class name:
public function testExpectedWriterStrategyReturned()
{
$mockWriter = $this->getMock(
'MyNamespace\Writer\Strategy\WriterStrategyInterface',
array(),
array(),
'SssWriter'
);
$file = 'extension.sss';
$writer = MyNamespace\Writer\WriterFactory::getWriterForFile($file);
$this->assertInstanceOf('MyNamespace\Writer\Strategy\WriterStrategyInterface', $writer);;
}
Is there any way for me to stage a mock writer strategy for the factory to load, or should I refactor the factory method to make it more testable?
The main purpose and responsibility for that factory is the creation of objects.
Imho you should test that using
$this->assertInstanceOf('class', $factory->getWriterForFile($file));
You could beimplementation in a way so that the factory dispatches the object creation to another class.
Having a "object creator" that does:
$class = new ReflectionClass($class);
$instance = $class->newInstanceArgs($args);
or something along those lines but what is your gain apart from "well the test looks more like a unit test"? You're not improving really improving your code base and changes just for test-abilities sake always smell a little funny to me.
I'd treat factory tests as integration/wiring tests that make sure all your factory actually work and produce the desired objects.
The only change I'd suggest for your current code sample would be to change two things:
If you don't have a very good reason you don't gain anything from a static factory except that you can't inject it and making it static makes it accessible from global scope so getting dependencies into the factory will require even more globals and so on. If you do DI with factories usually making them proper objects too helps avoiding issues down the road
The factory should not care about the file system. If it needs a existing file it should require one and leave the details of how to handle the error to the consumers.
That gives you the benefit that you can also pass a SplTempFileObject
which will make testing easier and allow your factories to be independent of the file system even for production purposes.