This directory contains the comprehensive PHPUnit test suite for the MAPI Header PHP library.
Install PHPUnit and development dependencies via Composer:
composer install --devThis installs:
- PHPUnit 9.x/10.x (depending on PHP version)
- PHP-CS-Fixer for code style
- Rector for compatibility checks
The test suite uses a PHP MAPI stub (dev/php-mapi-stub.php) that simulates the
MAPI extension without requiring the actual extension to be installed. This
allows tests to run in CI/CD environments.
Run the complete test suite:
vendor/bin/phpunitRun a single test file:
vendor/bin/phpunit tests/TokenTest.php
vendor/bin/phpunit tests/BaseExceptionTest.phpRun a specific test:
vendor/bin/phpunit --filter testTokenParsing tests/TokenTest.phpRun tests by group (if defined):
vendor/bin/phpunit --group authentication
vendor/bin/phpunit --exclude-group slowGenerate HTML coverage report (requires Xdebug or PCOV):
vendor/bin/phpunit --coverage-html coverageGenerate text coverage summary:
vendor/bin/phpunit --coverage-textGenerate Clover XML for CI:
vendor/bin/phpunit --coverage-clover coverage.xmlSee detailed test progress:
vendor/bin/phpunit --verbose
vendor/bin/phpunit --debugTests use phpunit.xml in the root directory. Key settings:
- Strict mode enabled (fails on warnings, risky tests)
- Fails on empty test suites
- Suppresses output during test execution
- Tracks coverage for all
.phpfiles except vendor/tests/dev
| File | Purpose | Coverage |
|---|---|---|
bootstrap.php |
Test bootstrap, loads MAPI stub | N/A |
BaseExceptionTest.php |
Tests for BaseException class | Exception handling |
MAPIExceptionTest.php |
Tests for MAPIException class | MAPI error mapping |
TokenTest.php |
Tests for Token (JWT) class | Token parsing |
ExtendedTokenTest.php |
Extended JWT functionality | Advanced tokens |
KeyCloakTest.php |
Tests for KeyCloak SSO | Authentication |
FreeBusyTest.php |
Tests for FreeBusy utilities | Free/busy ops |
UtilityFunctionsTest.php |
Tests for mapi.util.php | Utility functions |
tests/
├── bootstrap.php # PHPUnit bootstrap
├── README.md # This file
├── BaseExceptionTest.php
├── MAPIExceptionTest.php
├── TokenTest.php
├── ExtendedTokenTest.php
├── KeyCloakTest.php
├── FreeBusyTest.php
└── UtilityFunctionsTest.php
Follow these conventions when writing tests:
<?php
use PHPUnit\Framework\TestCase;
class MyFeatureTest extends TestCase {
/**
* @var MyClass
*/
private $instance;
protected function setUp(): void {
parent::setUp();
$this->instance = new MyClass();
}
protected function tearDown(): void {
$this->instance = null;
parent::tearDown();
}
/**
* Test that myMethod returns expected value.
*/
public function testMyMethodReturnsValue(): void {
$result = $this->instance->myMethod();
$this->assertIsString($result);
$this->assertEquals('expected', $result);
}
/**
* Test that myMethod throws exception on invalid input.
*/
public function testMyMethodThrowsExceptionOnInvalidInput(): void {
$this->expectException(InvalidArgumentException::class);
$this->instance->myMethod(null);
}
}-
Test Class Names:
{ClassName}Test.phpTokenTest.phpfor testingTokenclassBaseExceptionTest.phpfor testingBaseExceptionclass
-
Test Method Names:
test{MethodName}{Scenario}()testTokenParsing()- tests parsing functionalitytestTokenThrowsExceptionOnInvalidFormat()- tests exception scenariotestIsExpiredReturnsTrueWhenExpired()- descriptive scenario
-
Use descriptive names that explain what's being tested
Always use type hints:
public function testSomething(): void {
$value = $this->getValue();
$this->assertIsInt($value);
}
private function getValue(): int {
return 42;
}Use the most specific assertion available:
// Good
$this->assertSame(10, $value); // Strict type checking
$this->assertEquals('foo', $result); // Value equality
$this->assertInstanceOf(Token::class, $obj);
$this->assertTrue($condition);
$this->assertNull($value);
// Avoid
$this->assertEquals(true, $condition); // Use assertTrue instead
$this->assertEquals(null, $value); // Use assertNull insteadpublic function testExceptionThrown(): void {
$this->expectException(MAPIException::class);
$this->expectExceptionMessage('Invalid token');
$this->expectExceptionCode(MAPI_E_INVALID_PARAMETER);
$obj->methodThatThrows();
}Use data providers for testing multiple scenarios:
/**
* @dataProvider validTokenProvider
*/
public function testValidTokenParsing(string $token, array $expected): void {
$tokenObj = new Token($token);
$claims = $tokenObj->get_claims();
$this->assertEquals($expected, $claims);
}
public function validTokenProvider(): array {
return [
'simple token' => [
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
['sub' => '1234567890', 'name' => 'John Doe']
],
'expired token' => [
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...',
['sub' => '0987654321', 'exp' => 1234567890]
],
];
}For classes that depend on MAPI functions, use the stub:
public function testSomethingWithMAPI(): void {
// The bootstrap.php loads dev/php-mapi-stub.php
// which provides mock MAPI functions
// Your test code here
}Add clear documentation:
/**
* Test that Token::is_expired() returns true when the token has expired.
*
* This test creates a token with an expiration time in the past and
* verifies that is_expired() correctly identifies it as expired.
*/
public function testIsExpiredReturnsTrueForExpiredToken(): void {
// ...
}Group related assertions together:
public function testTokenProperties(): void {
$token = new Token($validJWT);
// Test all token components are set
$this->assertNotNull($token->token_header);
$this->assertNotNull($token->token_payload);
$this->assertNotNull($token->token_signature);
// Test header structure
$header = $token->token_header;
$this->assertArrayHasKey('typ', $header);
$this->assertArrayHasKey('alg', $header);
$this->assertEquals('JWT', $header['typ']);
}| Component | Target | Rationale |
|---|---|---|
| Critical utility functions | 100% | Core functionality |
| Token/KeyCloak classes | 90%+ | Authentication critical |
| Exception classes | 80%+ | Error handling paths |
| Recurrence classes | 70%+ | Complex MAPI dependencies |
| Meeting request classes | 70%+ | Complex MAPI dependencies |
Run this to see current coverage:
vendor/bin/phpunit --coverage-text --colors=never | grep -A 3 "Code Coverage Report"-
Identify gaps:
vendor/bin/phpunit --coverage-html coverage # Open coverage/index.html in browser -
Add tests for uncovered lines
-
Focus on:
- Edge cases
- Error conditions
- Boundary values
- Exception paths
Test individual methods in isolation:
public function testCalculateNextOccurrence(): void {
$recurrence = new Recurrence($store, $message);
$next = $recurrence->calculateNextOccurrence($baseDate);
$this->assertGreaterThan($baseDate, $next);
}Test interactions between classes:
public function testMeetingRequestWithRecurrence(): void {
$mr = new Meetingrequest($store, $message, $session);
$recurrence = new Recurrence($store, $message);
// Test that meeting request properly handles recurring meetings
// ...
}Always test:
- Null/empty inputs
- Boundary values (min/max)
- Invalid data formats
- Missing required properties
- Malformed data
When fixing bugs:
- Write a test that reproduces the bug
- Verify the test fails
- Fix the bug
- Verify the test passes
- Keep the test to prevent regression
Example CI configuration:
test:
script:
- composer install --dev
- vendor/bin/phpunit --coverage-text --colors=never
- vendor/bin/php-cs-fixer fix --dry-run --diffAdd to .git/hooks/pre-commit:
#!/bin/bash
vendor/bin/phpunit
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fiEnsure phpunit.xml is in the root directory and contains:
<testsuites>
<testsuite name="MAPI Header PHP Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>The tests/bootstrap.php should load dev/php-mapi-stub.php. Verify:
grep "php-mapi-stub.php" tests/bootstrap.phpInstall Xdebug:
pecl install xdebugOr use PCOV (faster):
pecl install pcovWhen contributing tests:
- Follow the naming conventions above
- Ensure tests are isolated (no dependencies on other tests)
- Use type hints and docblocks
- Add data providers for multiple scenarios
- Test both success and failure paths
- Update this README if adding new test categories