Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Hooks: setUp, tearDown & Skipping Tests

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

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

Login Subscribe

We can also use the TDD refactor step to improve our tests! Eventually, we're going to have a lot of test methods inside DinosaurFactoryTest. And each one will need to create the DinosaurFactory object. If that class eventually has some constructor arguments, that's going to be a pain!

The setUp Hook

Add a new $factory property and give it some PHPDoc: this will be a DinosaurFactory object. Then - here's the magic part - create a new public function setUp(). Inside, set the property to a new DinosaurFactory.

... lines 1 - 8
class DinosaurFactoryTest extends TestCase
{
/**
* @var DinosaurFactory
*/
private $factory;
... line 15
public function setUp()
{
$this->factory = new DinosaurFactory();
}
... lines 20 - 34
}

Back in our test method, use the new property. Yep, this will work... but only thanks to a bit of PHPUnit magic. If you have a method that's exactly called setUp, PHPUnit will automatically call it before each test.

... lines 1 - 8
class DinosaurFactoryTest extends TestCase
{
... lines 11 - 20
public function testItGrowsALargeVelociraptor()
{
$dinosaur = $this->factory->growVelociraptor(5);
... lines 24 - 28
}
... lines 30 - 34
}

If you have multiple test functions, that means that setUp will be called before each test method. This will make sure that the $factory property is a new, fresh DinosaurFactory object for every test. And that's really important: each test should be completely independent of each other. You never want one test to rely on something a different test set up first. Why? Because later, we'll learn how to execute just one test at a time - which is really useful for debugging.

Other Hooks: tearDown, setUpBeforeClass, etc

There are a few other magic methods like this. The most common is tearDown(), which is the opposite of setUp. It's still called once per test, but after the test is executed. It's meant for cleanup, and we'll talk more about it later.

Two other useful hook methods are setUpBeforeClass() and tearDownAfterClass. Instead of being called before or after every test, these are called once for the entire class. They're less common, but if you need to setup something global or static, this is the place to do it.

Oh, and one last, lesser-known hook method is onNotSuccessfulTest. Sometimes I'll use that to print extra debugging info.

Ok, make sure the tests still pass!

./vendor/bin/phpunit

Perfect!

Marking Tests as Incomplete

Our dinosaur park guests are really excited about seeing some triceratops! But... we can't grow them yet - the scientists are still working on that dino DNA.

But eventually, we're going to add a growTriceratops method to DinosaurFactory. To make sure we don't forget about this, let's start the test: testItGrowsATriceratops. But I don't really want this test to exist... and fail - that's lame. Instead, add $this->markTestIncomplete('Waiting for confirmation from GenLab').

... lines 1 - 8
class DinosaurFactoryTest extends TestCase
{
... lines 11 - 30
public function testItGrowsATriceraptors()
{
$this->markTestIncomplete('Waiting for confirmation from GenLab');
}
... lines 35 - 45
}

Try it!

./vendor/bin/phpunit

Nice! It's not a failure... just a clear marker to remind us that we have work to do!

Skipping Tests

A similar thing you can do is skip tests. Try this: add a new method: testItGrowsABabyVelociraptor(). Create a tiny velociraptor - adorable! - and make sure it's length is correct.

... lines 1 - 8
class DinosaurFactoryTest extends TestCase
{
... lines 11 - 35
public function testItGrowsABabyVelociraptor()
{
... lines 38 - 41
$dinosaur = $this->factory->growVelociraptor(1);
$this->assertSame(1, $dinosaur->getLength());
}
}

This will totally work. But let's pretend that, inside the growVelociraptor() method, we use some class or PHP extension that the user may or may not have installed. Check to see if some imaginary Nanny class exists. If it doesn't, we can't run the test! So mark it as skipped: there's nobody to watch the baby raptor!

... lines 1 - 8
class DinosaurFactoryTest extends TestCase
{
... lines 11 - 35
public function testItGrowsABabyVelociraptor()
{
if (!class_exists('Nanny')) {
$this->markTestSkipped('There is nobody to watch the baby!');
}
... lines 41 - 44
}
}
./vendor/bin/phpunit

When you run the tests now... cool! An I for incomplete and S for skipped.

I don't use markTestSkipped() in my own apps - it's a bit more useful when you're building some reusable library and need to write tests for optional features that use optional libraries. It's used all the time inside Symfony's core.

Next! I want to talk about my favorite feature in PHPUnit: data providers!

Leave a comment!

11
Login or Register to join the conversation
Steven L. Avatar
Steven L. Avatar Steven L. | posted 3 years ago | edited

setUp (and other methods) must be declared as void as of PHPUnit8:

`public function setUp(): void

{
         $this->factory = new DinosaurFactory();
}`
1 Reply

Thanks for sharing it :)

Reply
Sebastian R. Avatar
Sebastian R. Avatar Sebastian R. | posted 3 years ago

I have a problem,
you have request a non existent parameter 'PathImageCloud'

Reply
Sebastian R. Avatar

in windows and linux i have the equal problem... what i need to install?

Reply

Hey Sebastian R.!

Sorry about the troubles! Hmm... so you get an error that says this exactly?

you have request a non existent parameter 'PathImageCloud'

When do you get that error? There's actually nothing in our project that has anything like this... which makes me think that somehow some custom code got into your application and is causing this issue. Double check for a PathImageCloud in your code and let me know if you find anything :).

Cheers!

1 Reply
Sebastian R. Avatar

thanks for your help!!!
it is ok!

the error was in parameters.yml!

1 Reply
Sebastian R. Avatar

important!! please.

Reply

Hey Sebastian R.

Did you check Ryan's answer? https://symfonycasts.com/sc...

1 Reply
Sebastian R. Avatar
Sebastian R. Avatar Sebastian R. | MolloKhan | posted 3 years ago

ok!

Reply

I think there is 2 mistakes in this title : "Other Hooks: tearDown, setUpAfterClass, etf"

* setUpAfterClass => must be setUpBeforeClass or tearDownAfterClass
* etf => must be etc.

Reply

Hey AmalricBzh

Nice catch! I'm going to fix that right away :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

While the fundamentals of PHPUnit haven't changed, this tutorial *is* built on an older version of Symfony and PHPUnit.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.0, <7.4",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/orm": "^2.5", // v2.7.2
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "sensio/distribution-bundle": "^5.0.19", // v5.0.21
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.28
        "symfony/monolog-bundle": "^3.1.0", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.6.0
        "symfony/swiftmailer-bundle": "^2.3.10", // v2.6.7
        "symfony/symfony": "3.3.*", // v3.3.13
        "twig/twig": "^1.0||^2.0" // v2.4.4
    },
    "require-dev": {
        "doctrine/data-fixtures": "^1.3", // 1.3.3
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "liip/functional-test-bundle": "^1.8", // 1.8.0
        "phpunit/phpunit": "^6.3", // 6.5.2
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.4.30
    }
}
userVoice