PHPUnit
Annotations

 


Mon 11 Feb 2019


8.0.0


7.3.1


Sebastian Bergmann
Directory Structure
/var/www/demo-phpunit
|   composer.json
|   composer.lock
|   phpunit.xml
|
+---src
|   \---Demo
|           App.php
|           Email.php
|
\---tests
    \---Demo
            AppTest.php
            EmailTest.php
Code
final class Email { // namespace & use statements omitted
    private $email;

    private function __construct(string $email) {
        $this->ensureIsValidEmail($email);
        $this->email = $email;
    }

    public static function fromString(string $email): self {
        return new self($email);
    }

    public function __toString(): string {
        return $this->email;
    }

    private function ensureIsValidEmail(string $email): void {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException($email . ' not valid'));
        }
    }
}
Test Code
// namespace & use statements omitted
final class EmailTest extends \PHPUnit\Framework\TestCase {
    public function testCanBeCreatedFromValidEmailAddress(): void {
        $this->assertInstanceOf(
            Email::class,
            Email::fromString('user@example.com')
        );
    }

    public function testCannotBeCreatedFromInvalidEmailAddress(): void {
        $this->expectException(InvalidArgumentException::class);
        Email::fromString('invalid@');
    }

    public function testCanBeUsedAsString(): void {
        $this->assertEquals(
            'user@example.com',
            Email::fromString('user@example.com')
        );
    }
}
All tests passed
Some tests failed
final

https://ocramius.github.io/blog/when-to-declare-classes-final/

TL;DR: Make your classes always final, if they implement an interface, and no other public methods are defined.
@expectedException (& Code, Message, MessageRegExp)
final class EmailTest extends \PHPUnit\Framework\TestCase {
    public function testCannotBeCreatedFromInvalidEmailAddress(): void {
        $this->expectException(InvalidArgumentException::class);
        Email::fromString('invalid@');
    }
}
final class EmailTest extends \PHPUnit\Framework\TestCase {
    /**
     * @expectedException InvalidArgumentException
     */
    public function testCannotBeCreatedFromInvalidEmailAddress(): void {
        Email::fromString('invalid@');
    }
}
class MyTest extends \PHPUnit\Framework\TestCase {
    /**
     * @expectedException        MyException
     * @expectedExceptionCode    404
     * @expectedExceptionMessage Some Message
     */
    public function testExceptionHasRightCodeAndMessage()
    {
        throw new MyException('Some Message', 404);
    }
}
class MyTest extends \PHPUnit\Framework\TestCase {
    /**
     * @expectedException              MyException
     * @expectedExceptionMessageRegExp /Argument \d+ can not be an? \w+/
     */
    public function testExceptionHasRightMessage()
    {
        throw new MyException('Argument 2 can not be an integer');
    }
}
@group (@ticket, @small, @medium, @large)
class MyTest extends \PHPUnit\Framework\TestCase {
    /**
     * @group specification
     * @ticket jira1234
     */
    public function testSomething()
    {
    }

    /**
     * @group regression
     * @group bug2204
     */
    public function testSomethingElse()
    {
    }
}
./vendor/bin/phpunit --group regression
// phpunit.xml
<phpunit bootstrap="./vendor/autoload.php" colors="true" enforceTimeLimit="true"
    timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60">

    <testsuites>
        <testsuite name="Demo Tests">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">src</directory>
        </whitelist>
    </filter>

    <groups>
      <include>
        <group>regression</group>
      </include>
    </groups>
</phpunit>
class App {
    public function longRun() {
        sleep(2);
        return true;
    }
}

class AppTest extends \PHPUnit\Framework\TestCase {
    /**
     * @small
     */
    public function testShouldNotRunMoreThanASecond() {
        $app = new App();
        $result = $app->longRun();

        $this->assertEquals(true, $result);
    }
}
// composer.json
// @small needs PHP_Invoker which requires ext-pcntl (process control extension)
{
  "name": "zionsg/demo-phpunit",
  "require-dev": {
    "phpunit/phpunit": "^7.0",
    "phpunit/php-invoker": "^2.0"
  },
  "autoload": {
    "psr-4": {
      "Demo\\": "src/Demo/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "DemoTest\\": "test/Demo/"
    }
  },
  "scripts": {
    "test": "phpunit"
  }
}
@dataProvider, @testWith
class DataTest extends \PHPUnit\Framework\TestCase {
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected) {
        $this->assertSame($expected, $a + $b);
    }

    public function additionProvider() {
        return [
            'adding zeros'  => [0, 0, 0],
            'zero plus one' => [0, 1, 1],
            'one plus zero' => [1, 0, 1],
            'one plus one'  => [1, 1, 3], // this will fail
        ];
    }
}
class DataTest extends \PHPUnit\Framework\TestCase {
    /**
     * @param string $input
     * @param int    $expectedLength
     *
     * @testWith     ["test", 4]
     *               ["longer-string", 13]
     */
    public function testStringLength(string $input, int $expectedLength) {
        $this->assertSame($expectedLength, strlen($input));
    }
}
class DataTest extends \PHPUnit\Framework\TestCase {
    /**
     * @param array $array
     * @param array $keys
     *
     * An object representation in JSON will be converted into an associative array.
     * @testWith    [{"day": "monday", "conditions": "sunny"}, ["day", "conditions"]]
     */
    public function testArrayKeys($array, $keys) {
        $this->assertSame($keys, array_keys($array));
    }
}
@author, @after, @afterClass, @backupGlobals, @backupStaticAttributes, @before, @beforeClass, @codeCoverageIgnore, @covers, @coversDefaultClass, @coversNothing, @depends, @dataProvider, @doesNotPerformAssertions, @expectedException, @expectedExceptionCode, @expectedExceptionMessage, @expectedExceptionMessageRegExp, @group, @large, @medium, @preserveGlobalState, @requires, @runTestsInSeparateProcesses, @runInSeparateProcess, @small, @test, @testdox, @testWith, @ticket, @uses

@ @
~