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
- 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.
- Every test class mostly extends PHPUnit\Framework\TestCase.
- 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.
- 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.