DEV Community

Ethan Bray
Ethan Bray

Posted on • Originally published at ethanbray.com

2 2

PHPUnit: Returning values and throwing exceptions from the same mocked method

Recently I was writing a test case where an initial call to a method would return a value and a subsequent call would throw an exception. I assumed that it could be achieved by using a combination of will, willReturn and willThrowException.

$mockFoo     = $this->createMock(Foo::class);
$mockFactory = $this->createMock(FooFactory::class);

$mockFactory
    ->expects($this->exactly(2))
    ->method('make')
    ->with('Foo', 'Bar')
    ->will($this->willReturn($mockFoo), $this->throwException(new BarException()));

PHPUnit only supports one parameter to will and as such the exception was never thrown. I was surprised to find that PHPUnit has no documented solution for this issue and I instead resorted to scouring StackOverflow. I found this answer which put me on the right track.

Instead of creating the InvokedCount matcher and immediately passing it to expects, we can create it earlier and assign it to a variable. We can then replace our will method with willReturnCallback, using $matcher in the callback. InvokedCount provides the method getInvocationCount which allows us to track how many times the method make has been invoked. Using our callback, we can then handle each invocation separately.

$mockFoo     = $this->createMock(Foo::class);
$mockFactory = $this->createMock(FooFactory::class);
$matcher     = $this->exactly(2);

$mockFactory
    ->expects($matcher)
    ->method('make')
    ->with('Foo', 'Bar')
    ->willReturnCallback(function () use ($matcher, $mockFoo) {
        if ($matcher->getInvocationCount() === 1) {
            return $mockFoo;
        }

        throw new BarException();
    });

It's worth noting that this solution is not a replacement for willReturnOnConsecutiveCalls and is only for use when your mocked method need to do more than return values.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more