{"id":25227348,"url":"https://github.com/tyler36/phpunit-demo","last_synced_at":"2025-06-25T10:38:04.572Z","repository":{"id":276720596,"uuid":"809663291","full_name":"tyler36/phpunit-demo","owner":"tyler36","description":null,"archived":false,"fork":false,"pushed_at":"2025-05-27T00:15:30.000Z","size":208,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-27T01:27:08.302Z","etag":null,"topics":["github-workflow","php","phpunit","testing"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tyler36.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-06-03T07:59:09.000Z","updated_at":"2025-05-27T00:15:33.000Z","dependencies_parsed_at":"2025-05-19T05:21:26.695Z","dependency_job_id":"b255586f-f22c-41da-ae65-2c3e82c30dbd","html_url":"https://github.com/tyler36/phpunit-demo","commit_stats":null,"previous_names":["tyler36/phpunit-demo"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tyler36/phpunit-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyler36%2Fphpunit-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyler36%2Fphpunit-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyler36%2Fphpunit-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyler36%2Fphpunit-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tyler36","download_url":"https://codeload.github.com/tyler36/phpunit-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyler36%2Fphpunit-demo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261853286,"owners_count":23219837,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["github-workflow","php","phpunit","testing"],"created_at":"2025-02-11T08:55:11.527Z","updated_at":"2025-06-25T10:38:04.530Z","avatar_url":"https://github.com/tyler36.png","language":"PHP","readme":"# PHPunit \u003c!-- omit in toc --\u003e\n\n- [Overview](#overview)\n- [Install](#install)\n  - [Settings via `phpunit.xml`](#settings-via-phpunitxml)\n- [Outcomes](#outcomes)\n  - [Successful test](#successful-test)\n  - [Failure](#failure)\n  - [Error](#error)\n  - [Warning](#warning)\n  - [Risky](#risky)\n  - [Deprecated](#deprecated)\n  - [Notice](#notice)\n  - [Incomplete](#incomplete)\n  - [Skipped](#skipped)\n- [Usage](#usage)\n  - [Assertions](#assertions)\n  - [Expecting Exceptions](#expecting-exceptions)\n  - [Metadata](#metadata)\n  - [Disable Deprecation warnings](#disable-deprecation-warnings)\n  - [Data providers](#data-providers)\n  - [Testing output](#testing-output)\n    - [Marking output as risky](#marking-output-as-risky)\n  - [Test Doubles](#test-doubles)\n    - [Dummy](#dummy)\n    - [Stubs](#stubs)\n    - [Mocks](#mocks)\n  - [Mocking built-in PHP functions](#mocking-built-in-php-functions)\n- [Code coverage](#code-coverage)\n  - [XDEBUG](#xdebug)\n  - [PCOV](#pcov)\n  - [Risky coverage](#risky-coverage)\n- [Errors](#errors)\n  - [Fatal error: Class 'PHPUnit\\_Framework\\_TestCase' not found in](#fatal-error-class-phpunit_framework_testcase-not-found-in)\n\n## Overview\n\nPHPunit is the preferred package for testing PHP applications.\n\nHomepage: \u003chttps://phpunit.de/index.html\u003e\n\n## Install\n\n```bash\ncomposer require --dev phpunit/phpunit\n```\n\n### Settings via `phpunit.xml`\n\n- Configure a basic [`phpunit.xml`](./phpunit.xml) to reduce command-line switches.\n\n- `testsuites` are logically grouping of tests. For example: `Unit`, `Functional`.\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cphpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         colors=\"true\"\n\u003e\n    \u003ctestsuites\u003e\n        \u003ctestsuite name=\"Unit\"\u003e\n            \u003cdirectory\u003etests\u003c/directory\u003e\n        \u003c/testsuite\u003e\n    \u003c/testsuites\u003e\n\n    \u003csource\u003e\n        \u003cinclude\u003e\n            \u003cdirectory\u003eapp\u003c/directory\u003e\n        \u003c/include\u003e\n    \u003c/source\u003e\n\u003c/phpunit\u003e\n```\n\n## Outcomes\n\nCLI lists outcomes as followed:\n\n- `.`: Successful test with no issues.\n- `F`: Failure; assertion fails when running test method.\n- `E`: Error; an error or unexpected exception occurred.\n- `W`: Warning.\n- `R`: Risky.\n- `D`: Deprecation.\n- `N`: Notice.\n- `I`: Incomplete.\n- `S`: Skipped.\n\nUse `--stop-on-defect` to stop as soon as the runner encounters a non-passing test.\nEach non-passing type also has a `--stop-on-{outcome}` argument to target a specific outcome type.\nThis is available as a configuration option (`stopOn{outcome}`).\n\nFor non-fatal outcomes, a `failOn{Outcome}` configuration setting is available.\n\n### Successful test\n\nA test makes an assertion which is **true**.\n\n```php\nself::assertIsString('hello');\n```\n\n### Failure\n\nA test makes an assertion which is **false**.\nFailures imply the code is not working as expected or contains a bug.\n\n```php\nself::assertIsString(true);\n```\n\n### Error\n\nTest encounters a PHP error or unexpected exception.\nErrors signify the code is non-functional and invalid.\nFor example:\n\n- Missing classes.\n- Invalid types.\n- Unexpected exception.\n\n```php\n$app = new \\MissingClass();\n```\n\n@see [Expecting Exceptions](#expecting-exceptions)\n\n### Warning\n\nTriggered when a non-fatal error, runtime warning (`E_WARNING`), occurs.\n\n```php\nself::assertTrue(true);\ntrigger_error('non-fatal error was triggered', E_USER_WARNING);\n```\n\n### Risky\n\n[Risky tests](https://docs.phpunit.de/en/10.5/risky-tests.html#risky-tests) include:\n\n- Useless test, tests that do NOT perform assertions.\n  - Disable via `--dont-report-useless-tests` CLI\n  - Enable via `beStrictAboutTestsThatDoNotTestAnything=\"false\"` config.\n- Unintentional code coverage. @see [Code coverage](#code-coverage)\n- Output during tests. @see [Testing output](#testing-output)\n- Test execution timeout. @see [Test Execution Timeout](https://docs.phpunit.de/en/10.5/risky-tests.html#test-execution-timeout)\n- Global state manipulation\n  - Enable via `--strict-global-state` CLI\n  - Enable via `beStrictAboutChangesToGlobalState=\"true\"` config.\n\nTarget with:\n\n- `--stop-on-defect`\n- `--stop-on-risky`\n\n### Deprecated\n\nTriggered when test encounters a deprecation; `E_DEPRECATED`, `E_USER_DEPRECATED`, or PHPUnit deprecation.\n\n```php\ntrigger_error('example deprecation', E_USER_DEPRECATED);\n```\n\nTarget with:\n\n- `--stop-on-defect`\n- `--stop-on-deprecation`\n\n@see [Disable Deprecation warnings](#disable-deprecation-warnings)\n\n### Notice\n\nTriggered when test encounters a notice; `E_STRICT`, `E_NOTICE`, or `E_USER_NOTICE`.\n\n```php\ntrigger_error('non-fatal error was triggered', E_USER_WARNING);\n```\n\n### Incomplete\n\nUsing `$this-\u003emarkTestIncomplete('This test has not been implemented yet.');` serves as a placeholder.\nThe test, in its current state, is not meant to pass or failure.\n\n```php\n$this-\u003emarkTestIncomplete('// TODO: mark incomplete');\n```\n\n### Skipped\n\n[Skipped tests](https://docs.phpunit.de/en/10.5/writing-tests-for-phpunit.html#skipping-tests) are tests that are not required.\nFor example:\n\n- Tests for Mysql in a Postgres environment.\n- Tests not designed to work in CI or locally.\n- Tests not without require PHP extension.\n\nTo dynamically skip tests, use the following attributes:\n\n- `RequiresFunction(string $functionName)`\n- `RequiresMethod(string $className, string $functionName)`\n- `RequiresOperatingSystem(string $regularExpression)`\n- `RequiresOperatingSystemFamily(string $operatingSystemFamily)`\n- `RequiresPhp(string $versionRequirement)`\n- `RequiresPhpExtension(string $extension, ?string $versionRequirement)`\n- `RequiresPhpunit(string $versionRequirement)`\n- `RequiresSetting(string $setting, string $value)`\n\n## Usage\n\n### Assertions\n\n### Expecting Exceptions\n\nTo handle exceptions, use a `expectException()` assertion.\n\n```php\nclass Book {\n  public function setTitle(string $title){}\n}\n```\n\n```php\n$this-\u003eexpectException(ArgumentCountError::class);\n$book-\u003esetTitle();\n```\n\nAlso available are:\n\n- `expectExceptionCode()`\n- `expectExceptionMessage()`\n- `expectExceptionMessageMatches()`\n\n### Metadata\n\nPHPUnit 10+ embraces PHP attributes, instead of doc-block comments.\n@see \u003chttps://docs.phpunit.de/en/11.2/attributes.html\u003e.\n\n- Doc-block metadata.\n\n```php\n/**\n * Class CalculatorTest.\n *\n * @coversDefaultClass \\App\\Calculator\n * @group math\n */\nclass CalculatorTest extends TestCase\n```\n\n- PHP attributes.\n\n```php\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\nuse PHPUnit\\Framework\\Attributes\\Group;\n/**\n * Class CalculatorTest.\n */\n#[CoversClass(\\App\\Calculator::class)]\n#[Group('math')]\nclass CalculatorTest extends TestCase\n```\n\n### Disable Deprecation warnings\n\n- Configure Symfony's `Remaining direct deprecation notices` through `ENV` vars.\n\n```bash\nSYMFONY_DEPRECATIONS_HELPER=disabled vendor/bin/phpunit\n```\n\n```xml\n\u003cenv name=\"SYMFONY_DEPRECATIONS_HELPER\" value=\"weak\" /\u003e\n```\n\n- Disable with `SYMFONY_DEPRECATIONS_HELPER=disabled`\n- Show count with `SYMFONY_DEPRECATIONS_HELPER=weak`\n\n### Data providers\n\nData providers define an iterable (array, generator) that passes into the test function.\n\n- Use `PHPUnit\\Framework\\Attributes\\DataProvider` attribute.\n- The provider must return an array containing sets if data to pass into the test.\n- PHPunit display key name in error message, if applicable.\n\n```php\nclass CalculateTest extends \\PHPUnit\\Framework\\TestCase;\n{\n  public static function sumProvider(): array\n  {\n    return [\n      'zero' =\u003e [0,0,0],\n      'one' =\u003e [0,1,1]\n    ];\n  }\n\n  #[\\PHPUnit\\Framework\\Attributes\\DataProvider('sumProvider')]\n  public function testItAdds2Values($a = 1, $b = 2, $expected =3): void\n  {\n    $this-\u003eassertSame($expected, $a + $b);\n  }\n}\n```\n\nTo pass more than 1 data provider, stack them:\n\n```php\n    #[DataProvider('provideAddCases')]\n    #[DataProvider('provideNegativeAddCases')]\n    public function testItAdds(int $one, int $two, int $expected): void\n    {\n```\n\n### Testing output\n\nUse `expectOutputString()` to assert expected out (via `echo` or `print`).\n\n```php\npublic function testExpectFooActualFoo(): void\n{\n    $this-\u003eexpectOutputString('foo');\n\n    print 'foo';\n}\n```\n\n#### Marking output as risky\n\nPHPUnit can be strict about output during tests. Enable this via:\n\n- `--disallow-test-output` CLI\n- `beStrictAboutOutputDuringTests=\"true\"` config.\n\n### Test Doubles\n\n#### Dummy\n\nDummies are \"placeholders\". They help when initializing objects and will return NULL on method calls.\n\n```php\n$dummy = $this-\u003ecreateMock(SomeClass::class);\n\n// SUT - System Under Test\n$sut-\u003eaction($dummy);\n```\n\n#### Stubs\n\nStubs are is an \"object doubles\" and verify state. Key function is `willReturn()`.\n\nUse them to:\n\n- Test return values.\n- Fake return values.\n\n```php\n$calculator = $this-\u003ecreateStub(Calculator::class);\n\n$calculator-\u003emethod('add')\n  -\u003ewillReturn(3);\n\n$result = $calculator-\u003eadd(1, 2);\n$this-\u003eassertSame(3, $result);\n```\n\n#### Mocks\n\nUse mocks to verify behavior. Key function `expects()`, `shouldBeCalled()`, `shouldBeCalledTimes()`.\nUse to:\n\n- Count function calls.\n- Track function parameters.\n- What value it should return.\n- Throw exceptions, if required.\n\n```php\n    $calculator = $this-\u003ecreateMock(Calculator::class);\n\n    // `add(1,2)` should be called.\n    $calculator-\u003eexpects($this-\u003eonce())\n      -\u003emethod('add')\n      -\u003ewith(1, 2)\n      -\u003ewillReturn(3);\n\n    // 'zero()' should NOT be called.\n    $calculator-\u003eexpects($this-\u003enever())\n      -\u003emethod('zero');\n\n    $calculator-\u003eadd(1, 4);\n```\n\n### Mocking built-in PHP functions\n\nFor mocking returns or spies: [php-mock](https://github.com/php-mock/php-mock)\n\n- [php-mock/php-mock-phpunit](https://github.com/php-mock/php-mock-phpunit) - PHPUnit integration\n- [php-mock/php-mock-mockery](https://github.com/php-mock/php-mock-mockery) - Mockery integration\n- [php-mock/php-mock-prophecy](https://github.com/php-mock/php-mock-prophecy) - Prophecy (phpspec) integration\n\nBelow, we mock the `ldap_search` function to return false.\n\n```php\n$prophet = new PHPProphet();\n// Set the namespace our function will be called in.\n$prophecy = $prophet-\u003eprophesize('Drupal\\iq_ldap');\n$prophecy-\u003eldap_search(FALSE, 'cn=Users', 'samaccountname=test')-\u003ewillReturn(FALSE);\n$prophecy-\u003ereveal();\n...\n$prophet-\u003echeckPredictions();\n```\n\n## Code coverage\n\nCode coverage is a metric for test coverage of code.\nCode coverage is available via `xdebug` or `pcov`:\n\n- Xdebug: Slower, but include path coverage (Xdebug 2.3+).\n- POC: Faster, but unmaintained, requires patching (PHP8.4+). Line coverage.\n\n@see \u003chttps://thephp.cc/articles/pcov-or-xdebug\u003e\n\nNote:\nYou will see the following warning when trying to generate coverage reports without xdebug/pcov loaded.\n\n\u003c!-- textlint-disable stop-words,write-good --\u003e\nIf you see a \"Warning: XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set\":\n\u003c!-- textlint-enable stop-words,write-good --\u003e\n\nUse `CoversClass` attribute to target unit of code for coverage.\n\n```php\n#[CoversClass(Calculator::class)]\nclass CalculatorTest extends TestCase {...}\n```\n\nSet `low`/`high` coverage limits in `phpunit.dist.xml`.\n\n```xml\n\u003chtml outputDirectory=\"logs/php-coverage/html-coverage\" lowUpperBound=\"50\" highLowerBound=\"90\" /\u003e\n```\n\n### XDEBUG\n\nCLI:\n\n```shell\nXDEBUG_MODE=coverage phpunit\n```\n\nNote: PHPUnit config will _NOT_ accept this value because it needs to be active earlier in the bootstrap process.\nInstead, set the value in a composer script.\n\n```json\n  \"scripts\": {\n      \"test\": \"phpunit\",\n      \"test:coverage\": [\n          \"@putenv XDEBUG_MODE=coverage\",\n          \"@test --coverage-text\"\n      ]\n  },\n```\n\n### PCOV\n\n[PCOV homepage](https://github.com/krakjoe/pcov)\n\n1. Install PCOV. If you are using DDEV:\n\n```yml\n# .ddev/config.yaml\nwebimage_extra_packages: ['php${DDEV_PHP_VERSION}-pcov']\n```\n\nPCOV will target `src`, `lib`, or `app`. If you require coverage for other directories (`web`, `test`), configure `pcov.directory`.\nThis also resolves `0%` coverage results.\n\n- Directories should be PSR4 compliant.\n\n- For example. `/etc/php/8.1/cli/conf.d/21-pcov.ini` (PHP8.1)\n- To check PHP has loaded the file: `php --ini`\n\n```ini\n; 21-pcov.ini\n[pcov]\npcov.enabled = 1\npcov.directory = \"/var/www/html\"\npcov.exclude   = \"#/(vendor)/#\"\n```\n\nTo set via CLI:\n\n```shell\nphp -d pcov.enabled=1 -d pcov.directory=. -d pcov.exclude=\\\"~vendor~\\\" vendor/bin/phpunit --coverage-text\n```\n\nOr with a composer script:\n\n```json\n    \"scripts\": {\n        \"test\": \"phpunit\",\n        \"test:coverage\": [\n            \"php -d pcov.enabled=1 -d pcov.directory=\\\"/var/www/html\\\" -d pcov.exclude=\\\"~vendor~\\\" vendor/bin/phpunit\"\n        ]\n    }\n```\n\n### Risky coverage\n\nPHPunit can mark tests as `Risky` if they have unintentional code coverage.\n\n- Enable via `--strict-coverage` CLI\n- Enable via `beStrictAboutCoverageMetadata=\"true\"` config.\n\n## Errors\n\n### Fatal error: Class 'PHPUnit_Framework_TestCase' not found in\n\n- Older versions of PHPUnit used `extends PHPUnit_Framework_TestCase`\n- Newer versions should use `extends \\PHPUnit\\Framework\\TestCase`\n\n```diff\n- class ExampleTest extends PHPUnit_Framework_TestCase\n+ class ExampleTest extends\\PHPUnit\\Framework\\TestCase\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyler36%2Fphpunit-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftyler36%2Fphpunit-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyler36%2Fphpunit-demo/lists"}