phplaravelphpunitcode-coveragexdebug

Why Does Code Coverage for Custom Modules Fail in Laravel?


I set up PHPUnit to work with two different namespaces in a non-Laravel project, and it works perfectly. This is the folder structure of my working example:

├── docker-compose.yml
├── Dockerfile
├── composer.json
├── phpunit.xml
├── app/
│   └── Example.php
├── modules/
│   └── Example.php
└── tests/
    ├── AppTest.php
    └── ModulesTest.php

In this setup, phpunit.xml looks like this:

<testsuites>
    <testsuite name="Tests">
        <directory>tests</directory>
    </testsuite>
</testsuites>
<source>
    <include>
        <directory>app</directory>
        <directory>modules</directory>
    </include>
</source>

And the namespaces are defined in composer.json as follows:

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Modules\\": "modules/"
    }
}

I run the following command to generate the coverage report:

docker compose exec php ./vendor/bin/phpunit --coverage-html coverage-report

In this non-Laravel example, the code coverage report correctly shows both app and modules namespaces covered by tests.

Correct Code Coverage Report for Example App

You can checkout the full repository of my example at https://github.com/iwasherefirst2/poc-coverage

The Issue in Laravel:

When I replicate this setup in a fresh Laravel 10.48.24 project, with the same structure and configuration, the coverage report fails to include the modules folder, even though the tests for it pass successfully.

Failing Code Coverage Report for Laravel App

Here is the Laravel-specific information:

Both projects use the same PHPUnit version, and the tests in Laravel are plain unit tests (use PHPUnit\Framework\TestCase). However, the coverage report for my Laravel app only includes the app namespace, completely omitting modules.

Here is also the full repository to look at https://github.com/iwasherefirst2/poc-laravel-coverage-modules

What I Tried:

Key Observations:

Question: Why does the PHPUnit code coverage report fail to include the modules namespace in Laravel, even though the tests are running and passing? Could this be related to Laravel’s PHPUnit integration or autoload configuration? How can I resolve this and ensure full coverage for both namespaces in Laravel?


Solution

  • After thorough investigation, I identified that the difference between the Laravel repository and the non-Laravel repository was the use of PCOV during code coverage in the Laravel setup, while the non-Laravel repository relied on Xdebug.

    enter image description here

    enter image description here

    The Issue with PCOV

    PCOV appears to have issues handling code coverage for multiple directories at the root level. This caused the modules directory to be excluded from the coverage report in the Laravel repository.

    The Fix

    To resolve this issue, you can disable PCOV and use Xdebug.

    Option 1: Remove PCOV

    Modify your Dockerfile to exclude PCOV entirely. Simply remove the php8.2-pcov package from the installation step:

    RUN apt-get update && apt-get install -y \
        php8.2-cli \
        php8.2-xdebug \
        ...
        # Remove php8.2-pcov or don't include it at all
    

    Option 2: Manually Disable PCOV

    If removing PCOV is not an option, you can manually disable it during the coverage run by passing the pcov.enabled=0 directive:

    sail php -d pcov.enabled=0 ./vendor/bin/phpunit --coverage-text
    

    PHPUnit XML Configuration

    Ensure your phpunit.xml file includes the appropriate configuration for code coverage, depending on your PHPUnit version.

    For PHPUnit 9:

    Use the <filter> tag with a <whitelist> section:

    <filter>
        <whitelist>
            <directory suffix=".php">app</directory>
            <directory suffix=".php">modules</directory>
        </whitelist>
    </filter>
    

    For PHPUnit 10:

    Switch to the <source> tag with <include>:

    <source>
        <include>
            <directory suffix=".php">app</directory>
            <directory suffix=".php">modules</directory>
        </include>
    </source>