PHPUnit cheat sheet

Install PHPUnit

Install PHPUnit with composer

composer require --dev phpunit/phpunit ^10

Add php units into the symfony project.

symfony composer require --dev symfony/test-pack

Run Tests

Run our tests

./vendor/bin/phpunit tests

If you use a symfony framework:

symfony php bin/phpunit

Command line parameters

Most used:

--filter <pattern>      Use the <pattern> that matches the test class/method to specify the tests to be used.
--testdox               Report test execution progress in TestDox format.

Tests writing

Folder structure

Inside /tests lets create directory Unit (for our unit tests). Inside /tests/Unit we’ll mirroring all structure from /src.

Definitions, naming, conventions

  1. Test class — PHP class that contain tests for some functionality. Test class name must have “Test” postfix. Mostly uses one test class for one class, and named test class as original class name plus “Test” postfix.
  2. Every test class mostly extends PHPUnit\Framework\TestCase.
  3. Test method — it is a methods in test classes that contain functionality test. Test method name should have “test” prefix or must be marked with @test annotation or #[Test] attribute.
  4. For better convenience, the structure of the test directories should follow the structure of our class directories.
public function testSimpleAssert(): void
{
    Assert::assertEquals(1, 1, 'Number "one" not aquals to number "one"');
}

Asserts

What is a test? Test — it’s asserting expected and actual result of execution of some code. Main task in test it’s a make different assertions. PHP Unit provide a lot of asset method here are list of common used:

//Asserts that two variables have the same type and value. Used on objects, it asserts that two variables reference the same object.
assertSame($expected, $actual, string $message = '')

//Asserts that two variables do not have the same type and value. Used on objects, it asserts that two variables do not reference the same object.
assertNotSame($expected, $actual, string $message = '')

// Asserts that two variables are equal.
assertEquals($expected, $actual, string $message = ''): void

// Asserts that two variables are not equal.
assertNotEquals($expected, $actual, string $message = ''): void

// Asserts that a variable is null.
public static function assertNull($actual, string $message = ''): void

// Asserts that a variable is not null.
assertNotNull($actual, string $message = ''): void

// Asserts that a variable is of a given type.
assertInstanceOf(string $expected, $actual, string $message = ''): void

// Asserts that a condition is true.
assertTrue($condition, string $message = ''): void

// Asserts that a condition is false.
assertFalse($condition, string $message = ''): void

// Asserts the number of elements of an array, Countable or Traversable.
assertCount(int $expectedCount, $haystack, string $message = ''): void

// Asserts that an array has a specified key.
ssertArrayHasKey($key, $array, string $message = ''): void

// Asserts that a haystack contains a needle.
assertContains($needle, iterable $haystack, string $message = ''): void

Some development tricks when we write tests

If we don’t want to write test body with some assertions we can use mark test as incomplete.

public function testSomeFunctionality(): void
{
	$this->markTestIncomplete();
}

PHPUnit have other “mark” methods:

$this->markTestSkipped();
$this->markAsRisky();

Data providers

We can run test case many times with some arguments:

1. Add annotation @dataprovide dataProviderMethod or attribute
#[DataProvider(’dataProviderMethod’)] for our test case.

/** @dataProvider dataProviderMethod */
public function testSimpleAssert($arg1, $arg2): void
{
    Assert::assertEquals($arg1, $arg2);
}

2. Create dataProvideMethod

public function dataProviderMethod(): \Generator
{
    yield 'argument descriptions' => ['val1', 'val2'];
    yield 'yet another argument descriptions' => ['val_1', 'val_2'];
}

Create Mock

A mock object is a simulated object that mimics the behavior of a real object in a controlled way. It is a placeholder that stands in for the actual object being tested, providing a simplified and predictable version of its behavior.

Mock objects are often used in unit testing to isolate and test specific parts of a system without having to rely on the functionality of other, external components. By creating a mock object, developers can simulate the behavior of a real object and ensure that their code is working as intended without having to deal with the complexities of the real object.

Recommend mock only services not a value objects (object uses as arguments for method calling).

$mockMethodResult = ResultClass::create(10);
$mockObject = $this->createMock(ClassOrInterface::class);
$mockObject
		->expects(self::once()) // Expect method will be calling ones during test case
		->method('objectMethod') // Mock method calling objectMethod()
		->with('argument', ['a1' => 100], 'lastArgument') // With arguments
		->willReturn($mockMethodResult) // Will return $mockMethodResult object
;

Method calling expectations can be:

self::any() //method should calls any times
self::never() //method should not call 
self::atLeast(10) //method should calls at least 10 times
self::atLeastOnce() //method should calls at least one time
self::once() //method should call one time
self::exacly(10) //method should call 10 times
self::atMost(100) //method should at most 100 times

Exceptions test

Before method that throwing exceptions add expectations

$this->expectException(Exceprion::class); //Expected exception class
$this->expectExceptionMessage('Exceprions message'); //expected excepion message
$this->expectExceptionMessageMatches('/Expression.*/i');
$this->expectExceptionCode(1221); //expected exception code (integer)

setUp(), tearDown(), setUpBeforeClass(), tearDownAfterClass()

This “magic” methods — setUp(), tearDown(), setUpBeforeClass(), tearDownAfterClass() are calls before/after each test/class run. If you need some preparation/cleaning when tests running please implement this “protected” methods.