Back to Home

PHPUnit covers: navigation and test coverage for developers

The article explains how to use covers annotations and attributes in PHPUnit to improve navigation between tests and application code. It covers approaches to linking tests to methods, IDE setup, and coverage limitations.

Improving testing in PHP: guide to covers in PHPUnit
Advertisement 728x90

PHPUnit: Mastering Covers for Better Test Navigation and Coverage

Testing web controllers in PHP often leads to navigation headaches between tests and app code. Let's explore how PHPUnit's @covers annotations and attributes solve this, streamlining development without sacrificing coverage.

The Navigation Challenge Between Tests and Code

When testing controllers via HTTP requests, you can't directly call methods, making it tough to jump from test to code. For instance, a test for the /hello endpoint doesn't reference GreetingController::hello() directly. This forces manual IDE searches, slowing you down.

Here's an example controller and test:

Google AdInline article slot
final class GreetingController extends AbstractController
{  
    #[Route('/hello')]
    public function hello(): Response
    {
        return new Response($this->generate("Hello!"));
    }
}
class GreetingControllerTest extends WebTestCase
{
    public function test_hello(): void
    {
        $content = get('/hello');
        assert_starts_with('Hello!', $content);
    }
}

Three Ways to Link Tests to Code

Developers use various tricks to improve navigation.

1. Native Syntax with Reflection

Swap string paths for method calls via reflection, like request(GreetingController::hello(...)). Downside: Tests lose independence from app code, risking breakage on changes.

Google AdInline article slot

2. @see Annotation

Use PHPDoc to link:

/**
 * @see \App\Controller\GreetingController::hello()
 */
public function test_hello(): void { ... }

Pros: IDE support. Cons: Needs full class paths, generic tag with no special handling.

Google AdInline article slot

3. PHPUnit's Specialized Covers

Annotations or attributes built for coverage and navigation.

Using Covers Annotations in PHPUnit

Pre-PHPUnit 12, @coversDefaultClass and @covers ruled:

/**
 * @coversDefaultClass \App\Controller\GreetingController
 */
class GreetingControllerTest extends WebTestCase
{
    /**
     * @covers ::hello
     */
    public function test_hello(): void { ... }
}

Advantages:

  • Stellar PhpStorm support: autocomplete, navigation, usage detection.
  • Custom templates for test generation.

PhpStorm template downsides:

  • Limited to two PHPUnit templates.
  • Confusing names.
  • Occasional generator glitches.

Switching to Covers Attributes in PHPUnit 12+

PHPUnit 12 ditched annotations for attributes, but with a catch: they only apply to classes, not methods.

Basic Usage:

#[CoversClass('App\Controller\GreetingController')]
class GreetingControllerTest extends WebTestCase
{
    public function test_hello(): void { ... }
}

This hurts navigation precision without method specifics.

Specifying Methods with CoversMethod:

#[CoversMethod('App\Controller\GreetingController', 'hello')]
#[CoversMethod('App\Controller\GreetingController', 'bye')]
class GreetingControllerTest extends WebTestCase
{
    public function test_hello(): void { ... }
    public function test_bye(): void { ... }
}

Drawback: Attribute clutter for multi-method tests.

Best Strategy: One Method, One Test Class

Dedicated test classes per method simplify things:

#[CoversMethod('App\Controller\GreetingController', 'hello')]
class HelloTest extends WebTestCase
{
    public function test_hello(): void { ... }
    public function test_hello2(): void { ... }
}

Benefits:

  • Less mental overhead.
  • No per-method covers needed.
  • Promotes clean design: one controller, one endpoint.

Attribute templates work like annotations but need manual CoversMethod tweaks post-generation.

IDE Integration and Future Enhancements

PhpStorm already handles test-to-class jumps via Ctrl+Shift+T. Expect method-level navigation soon.

Key Takeaways

  • Use covers for navigation boosts, but mind coverage impact.
  • PHPUnit 12+ attributes are class-only, so adapt your testing.
  • Ideal: One test class per method for easy maintenance.
  • Set up IDE templates for auto-covers.
  • Watch PhpStorm updates for better attribute support.

Coverage Limits: Covers' Biggest Flaw

Covers' core job is scoping coverage to classes/methods. CoversClass excludes external code from reports—great for strict projects, but most devs just want to spot gaps.

Workarounds to disable:

  • Overkill Hack: Scripts stripping tags pre-test run.
  • Practical Fix: Patch phpunit/php-code-coverage via Composer.

Pick based on project needs and team prefs.

— Editorial Team

Advertisement 728x90

Read Next