Using test dependencies (Advanced)
When you begin writing tests for one of your classes you may notice that when one aspect of functionality for your class breaks, many tests fail. Quite often a method of a class will have some preconditions that must be true for it to behave properly in a given situation. A classic example of this is a stack. If you cannot construct a stack properly then any further tests against that stack are most likely going to fail.
You can use PHPUnit's test dependency feature to help with this. When you indicate that one test is dependent on another test, PHPUnit will skip the dependent test whenever its dependencies do not successfully pass. Test dependencies also allow you to enable producer-consumer relationships into your test suites. One test case will "produce" the input for another test case to "consume".
We will take a look at how test dependencies can work by writing a test for our CardCollection
class that looks at how cards are added to the deck.
How to do it...
Place the following code to the test/CardCollection.php
file:
<?php class CardCollectionTest extends PHPUnit_Framework_TestCase { private $cardCollection; public function setUp() { $this->cardCollection = new CardCollection(); } public function testCountOnEmpty() { $this->assertEquals(0, $this->cardCollection->count()); } /** * @depends testCountOnEmpty */ public function testAddCard() { $this->cardCollection->addCard(new Card('A', 'Spades')); $this->cardCollection->addCard(new Card('2', 'Spades')); $this->assertEquals(2, $this->cardCollection->count()); return $this->cardCollection; } /** * @depends testAddCard */ public function testGetTopCard(CardCollection $cardCollection) { $card = $cardCollection->getTopCard(); $this->assertEquals(new Card('2', 'Spades'), $card); } }
How it works...
In your new file you have two test methods using a @depends
annotation. This is the annotation that enables PHPUnit's test dependency functionality. This annotation, essentially, tells PHPUnit that you do not want to run the following test unless the test referenced in the @depends
annotation has passed. If this test has not passed then the following test will be skipped. If for some reason the CardCollection::count()
method was not running properly and caused the testCountOnEmpty()
test to fail then testAddCard()
would be skipped. This can be easily seen by breaking the testCountOnEmpty()
test on purpose by inserting $this|fail('testing @depends')
in the test and rerunning your tests.
Another interesting aspect of the @depends
annotation is the producer-consumer aspect of it. Whenever you mark a test with the @depends
annotation the return value from the test specified in the annotation will be provided as the argument to the test being annotated. This is what is happening in the testGetTopCard()
method. The testAddCard()
method returns the card collection being tested. This value then persists for any test that depends on this method. As soon as we annotated testGetTopCard()
with @depends testGetTopCard
, PHPUnit is triggered to pass the populated card collection as the first parameter.
This does a couple things for you. It doesn't bother to try and pull the top card if it appears that addCard()
is not working. It also prevents you from having to repeat the code necessary to populate your card collection.
Another thing to note is that the @depends
annotations always reference a test above the annotation. The @depends
annotation never influences the order of tests. Tests will always be run from the top of the file to the bottom of the file. If the @depends
annotation references a method below the annotation it will simply skip the test as the dependency has not yet passed.
Multiple test dependencies
You can add multiple @depends
annotations to a single test. PHPUnit will then check to ensure that all of the tests specified have passed before running a given test. If the dependencies also return values, they will all be accessible as arguments in the order they are specified. The following code shows how this works:
<?php class DependencyTest extends PHPUnit_Framework_TestCase { public function test1() { $this->assertTrue(true); return 1; } public function test2() { $this->assertTrue(true); return 2; } public function test3() { $this->assertTrue(true); return 3; } /** * @depends test1 * @depends test2 * @depends test3 */ public function testDependencies($arg1, $arg2, $arg3) { $this->assertEquals(1, $arg1); $this->assertEquals(2, $arg2); $this->assertEquals(3, $arg3); } }
Multiple dependent tests
You can also have the same test referenced by @depends
multiple times. You do need to be very careful when doing this. Copies are not made of any objects returned. So if you modify the object in any way in the first dependent test, those modifications will also be present in the second dependent test. This can be seen in the following code:
<?php class DependencyTest extends PHPUnit_Framework_TestCase { public function testCreateStdClass() { $obj = new stdClass(); $obj->foo = 'bar'; $this->assertTrue(true); return $obj; } /** * @depends testCreateStdClass */ public function testDependency1($obj) { $this->assertEquals('bar', $obj->foo); $obj->foo = 'notbar'; } /** * @depends testCreateStdClass */ public function testDependency2($obj) { $this->assertEquals('notbar', $obj->foo); } }