Advanced PHPUnit Testing: Annotations and Mocking

Share this article

In the first two parts of this series (Introduction to Unit Testing in PHP with PHPUnit and Getting to Know PHPUnit’s Assertions), I’ve walked you through some of the steps you can take to test your application. I showed you how, with the help of PHPUnit, you can write tests that are as simple or as complex as you need to ensure quality. By now, I hope you understand the importance of having a good set of unit tests. You know that, with those firmly in hand, you can change large parts of your application at will and be sure that the result will match 100%. In this third part of the series, I’m going to explore two features that are a bit more advanced, so they might not show up in your everyday unit-testing practice. PHPUnit has lots of advanced features that can be amazingly helpful when that special case comes around. This includes extending the framework itself, making test suites, building static datasets, and the focus of this article: annotations and mocking. Don’t worry if you’re unsure what either of these are; I’m here to help. I trust by the end that you’ll see how these two features can be useful to both you and your tests.

Annotations

If you’ve ever read a book or taken a note, you’ll be familiar with the idea of annotations. In the strictest sense, even the comments you put on your tests can be considered annotations. For example:
<?php

class MyTestClass extends PHPUnit_Framework_TestCase

{

/**

* Testing the answer to “do you love unit tests?”

*/

public function testDoYouLoveUnitTests()

{

$love = true;

$this->assertTrue($love);

}

}

?>
In the above example, you can see the simple comment block above the function definition. If you’ve been around PHP for any length of time, you’ve no doubt seen all sorts of commenting styles. This example is a “best practice” that several popular IDEs will use when inserting a comment. There’s nothing magical about it, and a normal /* */ or // will do just fine if you really want them. The trick comes when PHPUnit comes into the picture. Using the comment style from our example lets PHPUnit take some interesting actions for you automatically. PHPUnit has a set of annotations you can use, both in your actual tests and in test generation—that’s right, it can do some of the hard work for you. PHP lacks a way to parse the comments directly, so PHPUnit has its own tool that looks for certain phrases and characters to match. We’ll start with one of the more handy features—the annotations helping out in test generation. Let’s start with a sample class that does some basic math:
<?php

class MyMathClass

{

/**

* Add two given values together and return sum

*/

public function addValues($a,$b)

{

return $a+$b;

}

}

?>
Easy, right? I’m keeping it to a basic one-method class because I want you to be sure to catch everything going on. With this basic class, we want to be sure the laws of physics and number theory don’t ever change. So we want to write a test. Sure, we can write some code and knock the test out quickly, but why do that when we can be lazy and have PHPUnit do it for us? Thanks to the Skeleton Generator, we can point it at the class and it’ll do its best to make us a test. Since it’s just a part of the normal PHPUnit functionality, we just call phpunit, but with a special flag. Let’s save our simple class above in a local directory file called MyMathClass.php. To use the Skeleton Generator, just point it at the class file:
./phpunit –skeleton-test MyMathClass
A test file will be spit out the other side—MyMathClassTest.php—containing:
<?php

require_once '/path/to/MyMathClass.php';

/**

* Test class for MyMathClass.

* Generated by PHPUnit on 2011-02-07 at 12:22:07.

*/

class MyMathClassTest extends PHPUnit_Framework_TestCase

{

/**

* @var MyMathClass

*/

protected $object;

/**

* Sets up the fixture, for example, opens a network connection.

* This method is called before a test is executed.

*/

protected function setUp()

{

$this->object = new MyMathClass;

}

/**

* Tears down the fixture, for example, closes a network connection.

* This method is called after a test is executed.

*/

protected function tearDown()

{

}

/**

* @todo Implement testAddValues().

*/

public function testAddValues()

{

// Remove the following lines when you implement this test.

$this->markTestIncomplete(

'This test has not been implemented yet.'

);

}

}

?>
As you can see, it generates a lot of the basic methods and properties you might need. Yet, it does have some limitations. The generator sees that you have a custom method in there (addValues), but it doesn’t know what it does. Sure, our test is super simple, and it might be able to parse it and figure it out, but what about some of those other super-complex methods you have in your application. I know I wouldn’t want an automated tool trying to guess functionality and accidentally deleting my entire database in the process. No, PHPUnit’s smarter than that, taking the easy road: it makes you define it and marks it incomplete. For a large class, this could save you loads of time typing out each of the methods and generic test and class related details. Inside the annotations functionality, though, there’s a hidden gem that can make it all even easier. With the help of the @assert annotation, you can tell PHPUnit a bit about what’s inside your methods. Let’s take another look at our addValues method:
<?php

class MyMathClass

{

/**

* Add two given values together and return sum

* @assert (1,2) == 3

*/

public function addValues($a,$b)

{

return $a+$b;

}

}

?>
If we run our phpunit –skeleton-test command again, you’ll notice a difference in the file it creates for the test. Take a look at the last method:
<?php

/**

* Generated from @assert (1,2) == 3.

*/

public function testAddValues()

{

$this->assertEquals(

3,

$this->object->addValues(1,2)

);

}

?>
All of a sudden, PHPUnit has X-ray vision and can look inside your method! Well, sort of. Obviously, it can generate a test like this because we told it what the test was supposed to do. You can use the @assert annotation with most of the logic kinds of evaluations: equals, greater than, same, less than or equal to, and so forth. You can even stack them to have multiple assertions in your test. It’s limited to values and constants, though—you can’t use it to magically create objects in the generated test. Another handy annotation is @dataProvider, an easy way to push a standard set of data into your tests so that you always know what you’re working with. Clear as mud? Let me give you an example. We’ll test the functionality of an application with methods that rely only on input. That is to say, there’s no external XML files or databases it can pull the sample data from. Instead, we need a way to define a data set that all the tests can use. One option is to define a test property with some sample data in it and use it each time, but the @dataProvider
annotation makes it even simpler. Let’s stick with our simple addValues method in our MyMathClass class. This time, though, we’re going to introduce a new item—a simple data set replacing our hard-coded values:
<?php

/**

* Data provider for test methods below

*/

public static function provider()

{

return array(

array(1,2,3),

array(4,2,6),

array(1,5,7)

);

}

/**

* Testing addValues returns sum of two values

* @dataProvider provider

*/

public function testAddValues($a,$b,$sum)

{

$this->assertEquals(

$sum,

$this->object->addValues($a,$b)

);

}

?>
The major addition to our test is the provider method. Couple that with the @dataProvider annotation, and some fun happens. For each of those values in the array returned from provider, the test method is executed. This lets you check multiple sets of data against one test quite easily. The values are returned back into their corresponding parameters in the testAddValues method: array index 0 to $a, array index 1 to $b, and so on. The first two sets of data in my return array will pass just fine, but it seems I sometimes have a problem with addition, and my last data set is wrong: 1 + 5 does not equal 7. This shows that the data doesn’t always have to make the test pass. Sometimes it makes more sense to have “bad” data in the provider. This could be useful in test-driven development when you know the data it should respond to correctly, but there’s no support for it yet. These are just two of the handy annotations you can use in your testing. There’s quite a few others (you can find them in the PHPUnit manual) that do some really cool stuff:
  • The @expectedException annotation watches for an exception to be thrown. If it’s not, the test fails.
  • The @covers annotation can be useful for those worried about code coverage numbers and trying to be sure every bit of code is represented—even if one test covers three different methods effectively.
  • You can also use @depends to define relationships between your tests, if you require that the test the method relies on has passed. If it doesn’t, the following test is skipped.

Mocking

From here, we move on to the second more advanced topic: mocking. This feature is also called stubbing or test doubles. This one’s a little trickier than the annotations, so will take a bit more explaining. Just as before, we’ll set up a simple situation to gain a feel for what mocking has to offer. First, though, I’ll explain a bit of what mocking is. For those unfamiliar with the term, it comes from the practice of “mocking up” a test or sample version of an item. With PHPUnit’s mocking functionality, you make something that looks like the full object but, when you take a specific action, it only produces one result. It’ll make more sense if we just look at an example class and test. For our example, we’re going to be working with a database connection. Usually, the database is happy-go-lucky and queries return in under a second. There’s one query, though, that takes up so many resources that it’s just not worth running every time. It’s infamous for making scripts run long and can delay the execution of your unit tests (possibly leading to a delay in deployment). Thankfully, mocking can come to the rescue and set up a “mock object” for that method. Here’s the class:
<?php

class Database

{

/**

* This query always takes a really long time

*/

public function reallyLongTime()

{

// database query would return into $results

$results = array(

array(1,'test','foo value')

);

sleep(100);

return $results;

}

}

?>
Simple stuff, really—it’s a basic class to define a method for running a query that could take a while. For the sake of example, I’ve abbreviated this to a return statement. See that sleep call I tossed in there? That’s so we can more easily tell that we’re using the mock object instead of the real one. It simulates the delay in execution. Now, on to the fun part—the test. I’ll explain the various pieces after the code:
<?php

require_once '/path/to/Database.php';

class DatabaseTest extends PHPUnit_Framework_TestCase

{

private $db = null;

public function setUp()

{

$this->db = new Database();

}

public function tearDown()

{

unset($this->db);

}

/**

* Test that the "really long query" always returns values

*/

public function testReallyLongReturn()

{

$mock = $this->getMock('Database');

$result = array(

array(1,'foo','bar test')

);

$mock->expects($this->any())

->method('reallyLongTime')

->will($this->returnValue($result));

$return = $mock->reallyLongTime();

$this->assertGreaterThan(0,count($return));

}

}

?>
There’s a few new calls being made here; the easiest to spot is the getMock method the PHPUnit test case object includes. This method makes a call to create a pseudo-version of the object in question. In this case, it’s making a mock Database object that we can use in our test. Technically, this object can be used like any other. You can call other methods on it and have them return just like they would normally. The mocking functionality makes it easier to override that, though. For our long running query example, we’re making a mock object and overriding the reallyLongTime method for our own purposes. These few lines are the real key:
<?php

$mock->expects($this->any())

->method('reallyLongTime')

->will($this->returnValue($result));

?>
These three lines tell the mock object that anytime the reallyLongTime method is called on it, it should go ahead and return the value in $result instead. This bypasses the super-long wait time, and you’re still able to test and be sure the contents are returned correctly. Then, if someday you find out about the magic of indexes and caching, and you want to switch over to the actual object, it’s a snap. You can then call the reallyLongTime method on the mock object and retrieve your predefined result set. There’s lots of advanced fun to be had with the mocking functionality, including the use of the handy MockBuilder. It lets you make some calls that a basic mock doesn’t. In our example below, you can see how to create a new mocked object that doesn’t use the class’s constructor, as well as turns off any autoloading that might be in the works:
<?php

public function testReallyLongRunBuilder()

{

$stub = $this->getMockBuilder('Database')

->setMethods(array(

'reallyLongTime'

))

->disableAutoload()

->disableOriginalConstructor()

->getMock();

$result = array(array(1,'foo','bar test'));

$stub->expects($this->any())

->method('reallyLongTime')

->will($this->returnValue($result));

$this->assertGreaterThan(0,count($return));

}

?>
This works similarly to our previous examples, with the exception of those two new methods in there: disableAutoload and disableOriginalConstructor. Thankfully, they’re fairly descriptive about what they do. There are also several other return types besides returnValue technique. You can have the value returned from a callback (a PHP function or a custom one), return a series of results on consecutive calls to the mock object, and even throw an exception. The with method can be used to define what kind of input the mocked method can take (and can include some type checking too). For example:
<?php

/**

* Testing enforcing the type to "array" like the "enforceTypes"

* method does via type hinting

*/

public function ttestReallyLongRunBuilderConstraint()

{

$stub = $this->getMock('Database',array('reallyLongTime'));

$stub->expects($this->any())

->method('reallyLongTime')

->with($this->isType('array'));

$arr = array('test');

$this->assertTrue($stub->reallyLongRun($arr));

}

?>
In this case, the test would pass because I’m calling reallyLongRun with an array (checked with the isType call). If I were to call it on the stub with a string, PHPUnit would toss an error. If you ever find yourself needing to mock a class working with files on the local file system, PHPUnit will be unable to do it for you; however, they do recommend the vfsStream stream wrapper that can act as a go-between and simulate a file system.

Summary

I’ve given you a taste of two of the more powerful features that PHPUnit has to offer apart from normal day-to-day testing routines. Annotations give you more control over how your tests are being run and generated, and mocking gives you a powerful way to test how the code should work, without the hassle. There’s all sorts of other cool features tucked away in the PHPUnit manual just waiting for discovery. You can generate documentation from your test with TestDox, integrate your testing with Selenium, and generate code coverage numbers for your application to point you exactly where you need to test. From there you can move on to concepts like test-driven design (mentioned in a previous article), which can radically change the way you write applications. Unit testing is such an amazingly powerful tool, and having software like PHPUnit makes it an easy and enjoyable experience.

Frequently Asked Questions on Advanced PHPUnit Testing, Annotations, and Mocking

What are PHPUnit annotations and how are they used in testing?

PHPUnit annotations are special comments in your test code that can influence how your tests are run. They are used to provide metadata about your test methods and classes. For example, you can use the @test annotation to indicate that a method is a test method. Other annotations like @dataProvider and @depends can be used to provide test data or specify dependencies between test methods respectively. Annotations are a powerful tool in PHPUnit and can greatly enhance the flexibility and readability of your tests.

How can I use mocking in PHPUnit testing?

Mocking is a technique in PHPUnit testing where you create a ‘mock’ or ‘dummy’ version of an external or complex object that your code under test interacts with. This allows you to isolate the code you are testing and control its interactions with external code. PHPUnit provides a mocking framework that you can use to create and work with mock objects. You can create a mock object with the getMockBuilder() method and then specify how it should behave using methods like method(), willReturn(), etc.

What is the purpose of the @covers annotation in PHPUnit?

The @covers annotation in PHPUnit is used to specify which method(s) a test method is testing. This is particularly useful when you are using code coverage analysis. Code coverage analysis can tell you which parts of your code are not covered by your tests. However, it can sometimes give misleading results if your tests are indirectly testing code through other methods. By using the @covers annotation, you can tell PHPUnit exactly which methods your test method should be covering, giving you more accurate code coverage analysis.

How can I use data providers in PHPUnit?

Data providers are a feature in PHPUnit that allow you to run a test method multiple times with different sets of data. You can create a data provider by writing a method that returns an array of arrays. Each inner array is a set of data that will be used for one run of the test. You can then link this data provider to a test method with the @dataProvider annotation. The test method will then be run once for each set of data.

What is the difference between PHPUnit 9.6 and 10.3?

PHPUnit 9.6 and 10.3 are different versions of PHPUnit with different features and improvements. For example, PHPUnit 10.3 includes improvements to the type declarations of the PHPUnit\Framework\TestCase class, which can help you write more robust tests. It’s always a good idea to check the PHPUnit changelog or documentation to see what’s new in each version.

How can I install PHPUnit?

PHPUnit can be installed using Composer, a dependency management tool for PHP. You can install PHPUnit globally or locally for a specific project. To install it globally, you can run composer global require phpunit/phpunit. To install it for a specific project, navigate to your project directory and run composer require --dev phpunit/phpunit.

How can I run PHPUnit tests?

PHPUnit tests can be run from the command line. If you have installed PHPUnit globally, you can run your tests with the phpunit command followed by the path to your test file or directory. If you have installed PHPUnit locally for a specific project, you can run your tests with ./vendor/bin/phpunit followed by the path to your test file or directory.

How can I assert that an exception is thrown in PHPUnit?

You can use the expectException() method in PHPUnit to assert that a certain exception is thrown. You call this method in your test method before the code that is supposed to throw the exception. If the specified exception is thrown, the test passes. If not, the test fails.

How can I use the @depends annotation in PHPUnit?

The @depends annotation in PHPUnit is used to specify dependencies between test methods. If a test method depends on another test method, it will not be run if the other test method fails. This can be useful if a test method needs the result of another test method to run correctly. You can specify a dependency with the @depends annotation followed by the name of the test method the test depends on.

How can I use the @group annotation in PHPUnit?

The @group annotation in PHPUnit is used to group test methods together. This can be useful if you want to run a specific group of tests. You can specify a group with the @group annotation followed by the name of the group. You can then run only the tests in this group with the --group option followed by the group name when running your tests from the command line.

Chris CornuttChris Cornutt
View Author

Chris has been involved with PHP and its community for about eight years now, most of that running his site, PHPDeveloper.org - a site devoted to bringing the most up-to-date, informative news and community happenings to the forefront and, more recently, Joind.in, a community conference feedback service. He’s a co-organizer of his local PHP user group(DallasPHP), a Zend Certified Engineer and currently works developing web applications and APIs for a large hosting company in Dallas, Tx.

PHP
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week