Search code examples
symfony4phpunitprophecy

Test service in Symfony with private method


I trying to test public method in service but it calls another private method.

this is a test class

<?php

use App\Core\Application\Service\Files\UploadedFileService;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use App\Core\Infrastructure\FileStorage\Services\ImagePath;
use App\Core\Infrastructure\FileStorage\Services\ImageResizeGenerator;
use Symfony\Component\Routing\RouterInterface;

class UploadedFileServiceTest extends TestCase
{
    /** @var UploadedFileService */
    private $instance;

    private $parameterHandler;
    private $router;
    private $imageResizeGenerator;
    private $imagePath;

    public function setUp()
    {
        parent::setUp();

        $this->parameterHandler     = $this->prophesize(ParameterBagInterface::class);
        $this->router               = $this->prophesize(RouterInterface::class);
        $this->imageResizeGenerator = $this->prophesize(ImageResizeGenerator::class);
        $this->imagePath            = $this->prophesize(ImagePath::class);

        $this->instance = new UploadedFileService(
            $this->parameterHandler->reveal(),
            $this->router->reveal(),
            $this->imageResizeGenerator->reveal(),
            $this->imagePath->reveal()
        );
    }

    public function testGetDefaultImageResponse()
    {
        $result = $this->instance->getDefaultImageResponse('user');
    }

}

when I run testGetDefaultImageResponse test, in console log error apear.

this is tested function

/**
 * @param string $entity
 *
 * @return Response
 */
public function getDefaultImageResponse(string $entity)
{
    return new Response(
        $this->getDefaultImage($entity),
        Response::HTTP_OK,
        ['Content-type' => 'image/jpg']
    );
}

the real problem is in getDefaultImage() which throw error

file_get_contents(): Filename cannot be empty

this is the content of the private method

/**
 * @param string $entity
 *
 * @return bool|string
 */
private function getDefaultImage(string $entity)
{
    switch ($entity) {
        case 'entity1':
            return file_get_contents($this->parameterHandler->get('images.default_avatar'));
        case 'entity3':
            return file_get_contents($this->parameterHandler->get('images.default_logo'));
    }

    return file_get_contents($this->parameterHandler->get('images.default_avatar'));
}

how to set data to $this->parameterHandler->get('images.default_avatar')

Where I mistake in running tests? I must admit that I am rookie in Unit tests.


Solution

  • The problem is that your test mock, in this case the ParameterHandler prophet, mocks the method get with the default behavior, returning null. It wasn't told what to do when a method is called on it, therefor file_get_contents() will not receive a file path.

    First of all, you must tell your Prophet to return a proper file path:

        $this->parameterHandler = $this->prophesize(ParameterBagInterface::class);
        $this->parameterHandler->get('images.default_avatar')->willReturn('/your/path/avatar.jpg');
    

    This will now tell the Prophet to return /your/path/avatar.jpg if the method get() is called with the parameter images.default_avatar. This should work, if you are able to properly configure the path to your default avatar.

    You could even tell the Prophet that this method MUST be called by adding ->shouldBeCalled(), but then you would test internals of your actual tested class (there are pros and cons for that type of testing and depends on the test case):

        $this->parameterHandler->get('images.default_avatar')->willReturn('/your/path/avatar.jpg')->shouldBeCalled();
    

    The next challenge would probably be to abstract the call to file_get_contents() into a new class, which can be mocked as well (e.g. for speed and memory reasons).