If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe 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!
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.
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!
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!
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!
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!
I think there is 2 mistakes in this title : "Other Hooks: tearDown, setUpAfterClass, etf"
* setUpAfterClass => must be setUpBeforeClass or tearDownAfterClass
* etf => must be etc.
// 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
}
}
setUp (and other methods) must be declared as
void
as of PHPUnit8:`public function setUp(): void