This document outlines the testing standards and practices for contributions to StringFormatter.
- Use Data-Driven Testing: Create comprehensive data providers with descriptive string keys
- Test behaviors, not implementations: Focus on input/output results through public interfaces
- Use real instances: Avoid PHPUnit mocks - create custom test implementations when needed
- Black box testing: Verify interactions through results, not internal state
- All test classes must be
final - Use the
Respect\StringFormatter\Test\Unitnamespace - Add
#[CoversClass(ClassName::class)]attribute for code coverage - Extend
PHPUnit\Framework\TestCase
- Use
#[Test]attribute for test methods - Use
#[DataProvider('methodName')]for parameterized tests - Follow naming pattern:
itShould[Behavior]When[Condition]oritShould[ExpectedBehavior]
/ @return array<string, array{0: InputType, 1: TemplateType, 2: ExpectedType}> */
public static function providerForFeatureName(): array
{
return [
'descriptive test case name' => [
$input,
$template,
$expected,
],
'another test case' => [
$input2,
$template2,
$expected2,
],
];
}- Use descriptive string keys for each test case
- Document array structure with PHPDoc array shapes
- Include edge cases, error conditions, and real-world scenarios
- Test Unicode support and internationalization when applicable
Follow the Arrange-Act-Assert pattern for clear test organization:
#[Test]
public function itShouldFormatTemplateCorrectly(): void
{
// Arrange: Set up test data and objects
$parameters = ['name' => 'John'];
$template = 'Hello {{name}}!';
$expected = 'Hello John!';
// Act: Execute the method being tested
$formatter = new FormatterClass($parameters);
$actual = $formatter->format($template);
// Assert: Verify the result
self::assertSame($expected, $actual);
}- Arrange: Clearly separates test setup
- Act: Highlights the specific behavior being tested
- Assert: Makes verification explicit and easy to read
// Following Arrange-Act-Assert pattern:
// 1. Arrange: Create formatter and setup (done above)
// 2. Act: Call format method (done above)
// 3. Assert: Verify result
self::assertSame($expected, $actual);#[Test]
public function itShouldThrowExceptionForInvalidInput(): void
{
$this->expectException(InvalidFormatterException::class);
$this->expectExceptionMessage('Specific error message');
new FormatterClass($invalidInput);
}- Happy Path: Primary functionality with valid inputs
- Edge Cases: Empty inputs, boundary conditions, malformed data
- Error Conditions: Invalid inputs, exception scenarios
- Type Support: All PHP types when the class under test handler mixed types directly
- Unicode: International characters, emoji, mixed languages
- Real-world Usage: Email templates, log messages, URLs, etc.
- Each test method validates one specific behavior
- Group related tests logically by feature
- Keep test setup minimal and inline
- Use descriptive test case names in data providers
- Document test methods with PHPDoc when behavior isn't obvious
- Use type annotations for complex parameters in data providers
- Include real-world examples in test cases
- Create real instances of any objects needed for testing
- Use custom test implementations instead of PHPUnit mocks
- Test through public APIs like
format()andformatUsing()
class DumpStringifier implements Stringifier
{
public function stringify(mixed $value): string
{
return var_export($value, true);
}
}- Write tests that pass PHPStan static analysis
- Use proper type annotations in PHPDoc
- Ensure test code follows PSR-12 coding standard
See these test files for reference patterns:
tests/Unit/PlaceholderFormatterTest.php- Data-driven testing with comprehensive providerstests/Unit/PatternFormatterTest.php- Exception testing and edge case coveragetests/Unit/JavascriptFormatterTest.php- Unicode and internationalization testing
Remember: Tests should be clear, maintainable, and focused on verifying desired behaviors rather than implementation details.