Buy

Testing Exceptions

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The dinosaur enclosures are looking great. There's just one minor problem: people keep accidentally putting nice, gentle veggie-eating dinosaurs into the same enclosure as flesh-eating carnivores. The result is... well... expensive, and there's a lot of cleanup too.

We need to prevent the meat eaters and the veggie eaters from being mixed inside the same enclosure. In other words, if somebody tries to do this, we need to throw an exception!

The Custom Exception Class

It's optional, but let's create a custom exception class for this: NotABuffetException. I'll even give this a default message: people need to understand how horrible this is!

... lines 1 - 2
namespace AppBundle\Exception;
... line 4
class NotABuffetException extends \Exception
{
protected $message = 'Please do not mix the carnivorous and non-carnivorous dinosaurs. It will be a massacre!';
}

Making sure that this exception is thrown at the right time is critical to business. So let's write a test: testItDoesNotAllowCarnivorousDinosaursToMixWithHerbivores.

Inside the method, create this terrifying situation: $enclosure = new Enclosure() and $enclosure->addDinosaur(new Dinosaur()). By default, dinosaurs are non-carnivorous. So now, let's add a predator: new Dinosaur('Velociraptor') and true for the isCarnivorous argument.

... lines 1 - 9
class EnclosureTest extends TestCase
{
... lines 12 - 28
public function testItDoesNotAllowCarnivorousDinosToMixWithHerbivores()
{
$enclosure = new Enclosure();
$enclosure->addDinosaur(new Dinosaur());
... lines 34 - 36
$enclosure->addDinosaur(new Dinosaur('Velociraptor', true));
}
}

Expecting an Exception

At this point, an exception should be thrown. So... how can we test for that? By telling PHPUnit to expect an exception with... well... $this->expectException() and then the exception class: NotABuffetException::class. Make sure you add this before calling the final code.

... lines 1 - 9
class EnclosureTest extends TestCase
{
... lines 12 - 28
public function testItDoesNotAllowCarnivorousDinosToMixWithHerbivores()
{
... lines 31 - 34
$this->expectException(NotABuffetException::class);
... lines 36 - 37
}
}

If we've done our work correctly, this should fail. Try the test!

./vendor/bin/phpunit

Yes! Failed asserting that exception of type NotABuffetException is thrown.

Awesome! Let's go throw that exception! Inside Enclosure, at the bottom, add a new private function called canAddDinosaur with a Dinosaur argument. This will return a bool.

... lines 1 - 13
class Enclosure
{
... lines 16 - 40
private function canAddDinosaur(Dinosaur $dinosaur): bool
{
... lines 43 - 44
}
}

Here's some simple logic: return count($this->dinosaurs) === 0. So, if the enclosure is empty, then it's definitely ok to add a dinosaur. Or, check to see if $this->dinosaurs->first()->isCarnivorous() === $dinosaur->isCarnivorous(). If they match, we're good!

... lines 1 - 13
class Enclosure
{
... lines 16 - 40
private function canAddDinosaur(Dinosaur $dinosaur): bool
{
return count($this->dinosaurs) === 0
|| $this->dinosaurs->first()->isCarnivorous() === $dinosaur->isCarnivorous();
}
}

Back in addDinosaur(), if not $this->canAddDinosaur(). Throw the exception! Oh wait... make sure the class extends \Exception. My bad!

... lines 1 - 4
class NotABuffetException extends \Exception
{
... line 7
}

Now throw that exception!

... lines 1 - 13
class Enclosure
{
... lines 16 - 31
public function addDinosaur(Dinosaur $dinosaur)
{
if (!$this->canAddDinosaur($dinosaur)) {
throw new NotABuffetException();
}
... lines 37 - 38
}
... lines 40 - 45
}

Check the tests!

./vendor/bin/phpunit

Woo! We got it!

expect Exceptions via Annotations

There's one other way to test for exceptions. It's really the same, but looks fancier. Copy the test method and rename it so we can test for the opposite condition: testItDoesNotAllowToAddNonCarnivorousDinosaursToCarnivorousEnclosure. Wow that's a long name!

... lines 1 - 9
class EnclosureTest extends TestCase
{
... lines 12 - 42
public function testItDoesNotAllowToAddNonCarnivorousDinosaursToCarnivorousEnclosure()
{
... lines 45 - 48
}
}

Add the Velociraptor first and then remove expectException. Instead, add an annotation: @expectedException followed by the full class. PhpStorm puts the short name... so go copy the use statement and put it down here. Try it!

... lines 1 - 9
class EnclosureTest extends TestCase
{
... lines 12 - 39
/**
* @expectedException \AppBundle\Exception\NotABuffetException
*/
public function testItDoesNotAllowToAddNonCarnivorousDinosaursToCarnivorousEnclosure()
{
$enclosure = new Enclosure();
$enclosure->addDinosaur(new Dinosaur('Velociraptor', true));
$enclosure->addDinosaur(new Dinosaur());
}
}
./vendor/bin/phpunit

Yes! One more test passing.

I want to go through one more example next... and also add some security to the enclosures. Our guests have been terrorized enough.

Leave a comment!

  • 2018-02-13 Diego Aguiar

    Hey Boran Alsaleh

    Because when you add an expected exception like that, PHPUnit will wrap within a try-catch block your test method, so if it throws, it will check if the thrown exception matches to the one you specified, otherwise, the execution would explode (because you know, the code throwed an exception and nobody catched it)

    I hope it helps you understanding what's going on here :)

    Cheers!

  • 2018-02-13 Boran Alsaleh

    Hallo ,

    why we have to make sure adding $this->expectException(NotBuffetException::class); before our last code
    $Enclosure->addDinosaur(new Dinosaur('Carn Dinasour',true));

    thnx ,