7. Incomplete and Skipped Tests

Incomplete Tests

When you are working on a new test case class, you might want to begin by writing empty test methods such as:

  1. public function testSomething(): void
  2. {
  3. }

to keep track of the tests that you have to write. The problem with empty test methods is that they are interpreted as a success by the PHPUnit framework. This misinterpretation leads to the test reports being useless – you cannot see whether a test is actually successful or just not yet implemented. Calling $this->fail() in the unimplemented test method does not help either, since then the test will be interpreted as a failure. This would be just as wrong as interpreting an unimplemented test as a success.

If we think of a successful test as a green light and a test failure as a red light, we need an additional yellow light to mark a test as being incomplete or not yet implemented. PHPUnit\Framework\IncompleteTest is a marker interface for marking an exception that is raised by a test method as the result of the test being incomplete or currently not implemented. PHPUnit\Framework\IncompleteTestError is the standard implementation of this interface.

Example 7.1 shows a test case class, SampleTest, that contains one test method, testSomething(). By calling the convenience method markTestIncomplete() (which automatically raises an PHPUnit\Framework\IncompleteTestError exception) in the test method, we mark the test as being incomplete.

Example 7.1 Marking a test as incomplete

  1. <?php declare(strict_types=1);
  2. use PHPUnit\Framework\TestCase;
  3. final class SampleTest extends TestCase
  4. {
  5. public function testSomething(): void
  6. {
  7. // Optional: Test anything here, if you want.
  8. $this->assertTrue(true, 'This should already work.');
  9. // Stop here and mark this test as incomplete.
  10. $this->markTestIncomplete(
  11. 'This test has not been implemented yet.'
  12. );
  13. }
  14. }

An incomplete test is denoted by an I in the output of the PHPUnit command-line test runner, as shown in the following example:

  1. $ phpunit --verbose SampleTest
  2. PHPUnit 9.5.0 by Sebastian Bergmann and contributors.
  3. I
  4. Time: 0 seconds, Memory: 3.95Mb
  5. There was 1 incomplete test:
  6. 1) SampleTest::testSomething
  7. This test has not been implemented yet.
  8. /home/sb/SampleTest.php:12
  9. OK, but incomplete or skipped tests!
  10. Tests: 1, Assertions: 1, Incomplete: 1.

Table 7.1 shows the API for marking tests as incomplete.

Table 7.1 API for Incomplete Tests
MethodMeaning
void markTestIncomplete()Marks the current test as incomplete.
void markTestIncomplete(string $message)Marks the current test as incomplete using $message as an explanatory message.

Skipping Tests

Not all tests can be run in every environment. Consider, for instance, a database abstraction layer that has several drivers for the different database systems it supports. The tests for the MySQL driver can only be run if a MySQL server is available.

Example 7.2 shows a test case class, DatabaseTest, that contains one test method, testConnection(). In the test case class’ setUp() template method we check whether the MySQLi extension is available and use the markTestSkipped() method to skip the test if it is not.

Example 7.2 Skipping a test

  1. <?php declare(strict_types=1);
  2. use PHPUnit\Framework\TestCase;
  3. final class DatabaseTest extends TestCase
  4. {
  5. protected function setUp(): void
  6. {
  7. if (!extension_loaded('mysqli')) {
  8. $this->markTestSkipped(
  9. 'The MySQLi extension is not available.'
  10. );
  11. }
  12. }
  13. public function testConnection(): void
  14. {
  15. // ...
  16. }
  17. }

A test that has been skipped is denoted by an S in the output of the PHPUnit command-line test runner, as shown in the following example:

  1. $ phpunit --verbose DatabaseTest
  2. PHPUnit 9.5.0 by Sebastian Bergmann and contributors.
  3. S
  4. Time: 0 seconds, Memory: 3.95Mb
  5. There was 1 skipped test:
  6. 1) DatabaseTest::testConnection
  7. The MySQLi extension is not available.
  8. /home/sb/DatabaseTest.php:9
  9. OK, but incomplete or skipped tests!
  10. Tests: 1, Assertions: 0, Skipped: 1.

Table 7.2 shows the API for skipping tests.

Table 7.2 API for Skipping Tests
MethodMeaning
void markTestSkipped()Marks the current test as skipped.
void markTestSkipped(string $message)Marks the current test as skipped using $message as an explanatory message.

Skipping Tests using @requires

In addition to the above methods it is also possible to use the @requires annotation to express common preconditions for a test case.

Table 7.3 Possible @requires usages
TypePossible ValuesExamplesAnother example
PHPAny PHP version identifier along with an optional operator@requires PHP 7.1.20@requires PHP >= 7.2
PHPUnitAny PHPUnit version identifier along with an optional operator@requires PHPUnit 7.3.1@requires PHPUnit < 8
OSA regexp matching PHP_OS@requires OS Linux@requires OS WIN32|WINNT
OSFAMILYAny OS family@requires OSFAMILY Solaris@requires OSFAMILY Windows
functionAny valid parameter to function_exists@requires function imap_open@requires function ReflectionMethod::setAccessible
extensionAny extension name along with an optional version identifier and optional operator@requires extension mysqli@requires extension redis >= 2.2.0

The following operators are supported for PHP, PHPUnit, and extension version constraints: <, <=, >, >=, =, ==, !=, <>.

Versions are compared using PHP’s version_compare function. Among other things, this means that the = and == operator can only be used with complete X.Y.Z version numbers and that just X.Y will not work.

Example 7.3 Skipping test cases using @requires

  1. <?php declare(strict_types=1);
  2. use PHPUnit\Framework\TestCase;
  3. /**
  4. * @requires extension mysqli
  5. */
  6. final class DatabaseTest extends TestCase
  7. {
  8. /**
  9. * @requires PHP >= 5.3
  10. */
  11. public function testConnection(): void
  12. {
  13. // Test requires the mysqli extension and PHP >= 5.3
  14. }
  15. // ... All other tests require the mysqli extension
  16. }

If you are using syntax that doesn’t compile with a certain PHP Version look into the xml configuration for version dependent includes in The <testsuites> Element