Search code examples
phplaravelunit-testinglaravel-7

PHPUnit: I expect exception, it's being thrown but "$this->expectException" doesn't work


I want to test a particular method from my Laravel service class. When I test "a good case" all my assertions are green and everything pass, but I wanted to test an edge case when under some circumstances this method throws exception.

When I prepare my data and arguments to get this exception, indeed it's being thrown but I can't $this->expectException().

Service I want to test has 4 dependencies:

class TechMed3dSizeMeService extends AbstractService implements TechMed3dSizeMeServiceInterface
{
    private DatabaseManager $databaseManager;
    private FileSystem $fileSystem;
    private OrderServiceInterface $orderService;
    private ScanServiceInterface $scanService;

    public function __construct(
        DatabaseManager $databaseManager,
        FileSystem $fileSystem,
        OrderServiceInterface $orderService,
        ScanServiceInterface $scanService
    ) {
        $this->databaseManager = $databaseManager;
        $this->fileSystem = $fileSystem;
        $this->orderService = $orderService;
        $this->scanService = $scanService;
    }

In my test, within setUp() I mock them:

        $this->databaseManager = $this->getMockBuilder(DatabaseManager::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->fileSystem = $this->getMockBuilder(FileSystem::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->orderService = $this->getMockBuilder(OrderServiceInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->scanService = $this->getMockBuilder(ScanServiceInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $this->techMed3dSizeMeService = new TechMed3dSizeMeService(
            $this->databaseManager,
            $this->fileSystem,
            $this->orderService,
            $this->scanService,
        );

As you can see, the service I want to test is not mocked but I simply create new instance of it $this->techMed3dSizeMeService = new TechMed3dSizeMeService.

The method I want to test is:

    public function getUrlScheme(int $orderId): string
    {
        $order = $this->orderService->getOrderById($orderId);

        if (!$order instanceof Order) {
            throw new ServiceResourceNotFoundException(); // I want to get this exception in assertion
        }

        if (!$this->canAccessByOwnershipOrSufficientRole($order)) {
            throw new ServiceAuthorizationException();
        }

        if (!$order->canObtainScansFromExternalApp) {
            throw new OrderNotReadyToObtainScansFromExternalAppException(
                'Requested Order did not pass through required steps to obtain scans.'
            );
        }

        $orderDetails = $order->orderDetailsOrders;

        if (!$orderDetails instanceof OrderDetails) {
            throw new OrderDoesNotHaveOrderDetails(
                'Requested Order does not have Order Details necessary to obtain scans.'
            );
        }

        return sprintf(
            'tm3d-in-3dsizeme://data?params=%s',
            $this->generatePayloadForUrlScheme($order, $orderDetails)
        );
    }

Good case passes:

    public function get_url_scheme_for_given_order_owned_by_prosthetist()
    {
        // Act as
        $this->actingAs($this->userProsthetist);

        // Create necessary data
        $order = $this->createOrderThanCanObtainScansForUser($this->userProsthetist);
        $this->createOrderDetailsForOrder($order);

        // Invoke
        $this->orderService->expects($this->once())
            ->method('getOrderById')
            ->will($this->returnValue($order));

        // Assert
        $urlScheme = $this->techMed3dSizeMeService->getUrlScheme($order->id);

        $this->assertTrue(Str::startsWith($urlScheme, 'tm3d-in-3dsizeme://data?params='));
        $this->assertTrue(Str::endsWith($urlScheme, '='));
    }

For bad case I provide non-existing Order ID and I expect to get Exception:

    public function get_url_scheme_for_given_order_owned_by_prosthetist_where_order_does_not_exist()
    {
        // Act as
        $this->actingAs($this->userProsthetist);

        // Invoke
        $this->orderService->expects($this->once())
            ->method('getOrderById')
            ->will($this->returnValue(null));

        // Assert
        $urlScheme = $this->techMed3dSizeMeService->getUrlScheme(999999); // I provide non existing ID

        $this->expectException(ServiceResourceNotFoundException::class); // I want to get this and I get but this doesn't work
    }

Result:

bash-5.0# ./vendor/bin/phpunit --filter=TechMed3dSizeMeServiceTest
PHPUnit 8.5.13 by Sebastian Bergmann and contributors.

.E                                                                  2 / 2 (100%)

Time: 5.15 seconds, Memory: 28.00 MB

There was 1 error:

1) Tests\Unit\Services\TechMed3dSizeMeService\TechMed3dSizeMeServiceTest::get_url_scheme_for_given_order_owned_by_prosthetist_where_order_does_not_exist
App\Exceptions\Services\ServiceResourceNotFoundException:

/app/app/Services/TechMed3dSizeMeService/TechMed3dSizeMeService.php:45
/app/tests/Unit/Services/TechMed3dSizeMeService/TechMed3dSizeMeServiceTest.php:53

ERRORS!
Tests: 2, Assertions: 3, Errors: 1.
bash-5.0#

It looks like the exception is thrown but not picked by PHPUnit? What am I doing wrong? I shouldn't use new?


Solution

  • just move the $this->expectException(ServiceResourceNotFoundException::class); to before $urlScheme = $this->techMed3dSizeMeService->getUrlScheme(999999);

    public function get_url_scheme_for_given_order_owned_by_prosthetist_where_order_does_not_exist()
    {
        // Act as
        $this->actingAs($this->userProsthetist);
    
        // Invoke
        $this->orderService->expects($this->once())
            ->method('getOrderById')
            ->will($this->returnValue(null));
    
    
        // Assert
        $this->expectException(ServiceResourceNotFoundException::class);
        $urlScheme = $this->techMed3dSizeMeService->getUrlScheme(999999); // I provide non existing ID
    }