Data Providers

Sooner or later you'll want to test the same logic against several different inputs. Without data providers, that usually means copy-pasting a test method three, four, five times, changing only the input and expected value each time. A data provider lets you write the test logic once and hand it a whole table of input/expected-output pairs — PHPUnit runs the test once per row automatically.

The Problem: Repetitive Test Methods

Imagine testing an isEven() function. Without a data provider, you might end up writing something like this — the same three lines, repeated for every case:

<?php
use PHPUnit\Framework\TestCase;

class IsEvenTest extends TestCase
{
    public function testTwoIsEven()
    {
        $this->assertTrue(isEven(2));
    }

    public function testThreeIsNotEven()
    {
        $this->assertFalse(isEven(3));
    }

    // ...and so on for every case you want to cover
}

Every one of these methods is doing exactly the same thing — calling isEven() and asserting the result. A data provider collapses all of that into one test method plus one array of cases.

Writing a Data Provider (Modern Attribute Syntax)

On PHPUnit 10+ with PHP 8, the recommended way to attach a provider is the #[DataProvider(...)] attribute placed directly above the test method. The provider method itself returns an array of arrays — each inner array is one set of arguments that gets passed to the test method for one run.

<?php
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class IsEvenTest extends TestCase
{
    #[DataProvider('numberProvider')]
    public function testIsEven(int $number, bool $expected)
    {
        $this->assertSame($expected, isEven($number));
    }

    public static function numberProvider(): array
    {
        return [
            // [number, expected result]
            'zero is even' => [0, true],
            'two is even' => [2, true],
            'three is odd' => [3, false],
            'negative four is even' => [-4, true],
            'negative seven is odd' => [-7, false],
        ];
    }
}

One testIsEven() method, five data sets, five separate test runs — no copy-pasting required. Giving each row a descriptive string key (like 'negative four is even') instead of leaving it a plain numeric index makes failure output much easier to read.

Note: The provider method must be public (or public static, which is preferred since it doesn't depend on any test instance state), and it must return either an array of arrays or a generator that yields arrays. Each inner array's items are passed as positional arguments to the test method, in order.

The Older Annotation Syntax: @dataProvider

Before PHP 8 attributes existed, and still extremely common in legacy codebases and older PHPUnit versions, the same thing is written as a @dataProvider tag inside the test method's docblock comment. The provider method itself looks identical — only how it's attached to the test changes.

<?php
use PHPUnit\Framework\TestCase;

class IsEvenTest extends TestCase
{
    /**
     * @dataProvider numberProvider
     */
    public function testIsEven($number, $expected)
    {
        $this->assertSame($expected, isEven($number));
    }

    public static function numberProvider()
    {
        return [
            [0, true],
            [2, true],
            [3, false],
        ];
    }
}

You'll run into this docblock form constantly when reading older projects or tutorials, so it's worth recognizing even though the attribute form is the modern recommendation for new code on PHP 8+.

How Failures Are Reported

When one data set fails, PHPUnit doesn't just say "testIsEven failed" and leave you guessing which input caused it. It reports the specific data set — by its string key if you gave it one, otherwise by its numeric index — right alongside the test name, so you can jump straight to the failing case.

1) IsEvenTest::testIsEven with data set "three is odd" (3, false)
Failed asserting that true is identical to false.

/path/to/tests/IsEvenTest.php:10

That single line tells you exactly which row of the provider array broke, without having to hunt through a wall of duplicated test methods to find it.

ApproachWhen you'll see it
#[DataProvider('method')] attributePHPUnit 10+, PHP 8+ codebases — the modern recommendation
@dataProvider method docblock annotationLegacy code, older PHPUnit versions, many existing tutorials
Next up: Data providers cover multiple inputs to the same class. The next page, Mocking Objects, covers isolating a class from its real-world dependencies so a unit test doesn't need a real database or API call.