2

I'm using PHPUnit ~5.2 and PHP ~7.0.0.

I have a class that wraps a repository, catches the exceptions it throws, calls a logger and then rethrows the exception.

public function storeDonation( Donation $donation ) {
    try {
        $this->repository->storeDonation( $donation );
    }
    catch ( StoreDonationException $ex ) {
        $this->logger->log( $this->logLevel, $ex->getMessage(), [ 'exception' => $ex ] );
        throw $ex;
    }
}

In my tests I've created a spy for the logger, so I can see which calls where made to the logging functions. This spy is a dedicated test double NOT created via the PHPUnit mocking API. The issue I'm running into would not occur when using the PHPUnit mocking API, however I'm not asking about feedback for that decision here.

The test method where I'm running into problems has some setup code, then it has a call to PHPUnits expectException (which used to be setExpectedException in PHPUnit 5.1 and older), then calls the production method, and finally gets the logging calls from the spy to assert the right ones where made.

public function testWhenGetDonationByIdThrowException_itIsLoggedAndThrown() {
    $logger = new LoggerSpy();

    $loggingRepo = new LoggingDonationRepository(
        $this->newThrowingRepository(),
        $logger
    );

    $this->expectException( GetDonationException::class );
    $loggingRepo->getDonationById( 1337 );

    $this->assertEquals(
        [
            [
                LogLevel::CRITICAL,
                self::GET_DONATION_BY_ID_EXCEPTION_MESSAGE,
                $this->newGetDonationException()
            ]
        ],
        $logger->getLogCalls()
    );
}

The problem is that when the exception gets thrown, PHPUnit catches it and evaluates expectException, after which execution of my test method does not continue. The assertions using my spy thus never run.

I'm looking for a way to solve this. I've considered splitting the method into one that has the expectException part, and another that has both a dummy try-catch for the production code call and the assertions for the spy. Any suggestions of nicer approaches?

Jeroen De Dauw
  • 8,671
  • 10
  • 46
  • 71

1 Answers1

4

When you throw an exception, all of the following code is interrupted until a catch statement. That also goes for your test method.

PHPUnit always catches the exceptions so it can log them, and by using expectedException you just tell it to handle the exception that has been thrown a little different.

So yes, you will need to write your own try catch block if you want to want to do other checks after the exception has been thrown. Here's an example of how you'd do that:

public function testWhenGetDonationByIdThrowException_itIsLoggedAndThrown() {
    $logger = new LoggerSpy();

    $loggingRepo = new LoggingDonationRepository(
        $this->newThrowingRepository(),
        $logger
    );

    try {
        $loggingRepo->getDonationById( 1337 );
    } catch (GetDonationException $e) {
        $this->assertEquals(
            [[
                LogLevel::CRITICAL,
                self::GET_DONATION_BY_ID_EXCEPTION_MESSAGE,
                $this->newGetDonationException()
            ]],
            $loger->getLogCalls()
        );

        return;
    }

    $this->fail('Expected exception wasn\'t thrown!');
}
Kuba Birecki
  • 2,683
  • 1
  • 12
  • 15