Testing Guide
This guide covers how to test applications using the Derafu Translation library, focusing on testing translatable exceptions and message handling.
Testing Exceptions
Basic Exception Testing
use Derafu\Translation\TranslatableMessage;
use PHPUnit\Framework\TestCase;
class ValidationExceptionTest extends TestCase
{
public function testBasicException(): void
{
$exception = new ValidationException('Email is invalid.');
// Without translation, should use original message.
$this->assertEquals('Email is invalid.', $exception->getMessage());
}
public function testExceptionWithParameters(): void
{
$exception = new ValidationException([
'validation.email',
'email' => 'test@example',
]);
// Test ICU formatting without translator.
$this->assertEquals(
'Invalid email: test@example',
$exception->getMessage()
);
}
public function testWithTranslatableMessage(): void
{
$message = new TranslatableMessage(
'validation.required',
['field' => 'email'],
);
$exception = new ValidationException($message);
$this->assertInstanceOf(
TranslatableMessage::class,
$exception->getTranslatableMessage()
);
}
}
Using Mock Translator
class OrderExceptionTest extends TestCase
{
private MockObject&TranslatorInterface $translator;
protected function setUp(): void
{
$this->translator = $this->createMock(TranslatorInterface::class);
}
public function testOrderValidation(): void
{
$this->translator
->expects($this->once())
->method('trans')
->with(
'order.invalid_status',
['status' => 'pending'],
'errors',
'en'
)
->willReturn('Invalid order status: pending');
$exception = new OrderException([
'order.invalid_status',
'status' => 'pending'
]);
$this->assertEquals(
'Invalid order status: pending',
$exception->trans($this->translator, 'en')
);
}
}
Testing Translations
Testing Message Files
class TranslationFilesTest extends TestCase
{
private string $fixturesDir;
protected function setUp(): void
{
$this->fixturesDir = __DIR__ . '/../fixtures/translations';
}
#[DataProvider('dataProvider')]
public function testMessageFiles(string $file): void
{
$this->assertFileExists($file);
// Test file format.
$messages = require $file;
$this->assertIsArray($messages);
// Test message format.
foreach ($messages as $key => $value) {
$this->assertIsString($key);
$this->assertIsString($value);
// Test ICU format.
$formatter = new MessageFormatter('en', $value);
$this->assertNotFalse(
$formatter,
"Invalid ICU format in message: $value"
);
}
}
public function provideMessageFiles(): array
{
return [
'en messages' => [$this->fixturesDir . '/messages/en.php'],
'es messages' => [$this->fixturesDir . '/messages/es.php'],
'en errors' => [$this->fixturesDir . '/errors/en.php'],
'es errors' => [$this->fixturesDir . '/errors/es.php'],
];
}
}
Testing Translation Consistency
class TranslationConsistencyTest extends TestCase
{
private array $locales = ['en', 'es'];
private array $domains = ['messages', 'errors'];
private MessageProviderInterface $provider;
protected function setUp(): void
{
$this->provider = new PhpMessageProvider(
__DIR__ . '/../fixtures/translations'
);
}
public function testAllLocalesHaveSameKeys(): void
{
foreach ($this->domains as $domain) {
$baseMessages = $this->provider->getMessages('en', $domain);
$baseKeys = array_keys($baseMessages);
foreach ($this->locales as $locale) {
if ($locale === 'en') {
continue;
}
$messages = $this->provider->getMessages($locale, $domain);
$keys = array_keys($messages);
$this->assertEquals(
sort($baseKeys),
sort($keys),
"Missing translations in $locale for domain $domain"
);
}
}
}
public function testIcuParametersConsistency(): void
{
foreach ($this->domains as $domain) {
$baseMessages = $this->provider->getMessages('en', $domain);
foreach ($this->locales as $locale) {
if ($locale === 'en') {
continue;
}
$messages = $this->provider->getMessages($locale, $domain);
foreach ($baseMessages as $key => $baseMessage) {
$message = $messages[$key];
// Extract parameters from ICU messages.
preg_match_all('/{(\w+)}/', $baseMessage, $baseParams);
preg_match_all('/{(\w+)}/', $message, $params);
$this->assertEquals(
sort($baseParams[1]),
sort($params[1]),
"Parameters mismatch in key '$key' for locale $locale"
);
}
}
}
}
}
Testing Custom Providers
class CustomProviderTest extends TestCase
{
public function testProvider(): void
{
$provider = new CustomProvider();
// Test basic functionality.
$messages = $provider->getMessages('en');
$this->assertIsArray($messages);
// Test locales.
$locales = $provider->getAvailableLocales();
$this->assertNotEmpty($locales);
// Test domains.
$errors = $provider->getMessages('en', 'errors');
$this->assertIsArray($errors);
}
public function testErrorHandling(): void
{
$provider = new CustomProvider();
$this->expectException(RuntimeException::class);
$provider->getMessages('invalid-locale');
}
}
Test Helpers
trait TranslationTestTrait
{
private function createTestTranslator(): TranslatorInterface
{
return new class implements TranslatorInterface {
public function trans(
?string $id,
array $parameters = [],
string $domain = null,
string $locale = null
): string {
return strtr($id, $parameters);
}
public function getLocale(): string
{
return 'en';
}
};
}
private function assertTranslationEquals(
string $expected,
TranslatableException $exception,
string $message = ''
): void {
$translator = $this->createTestTranslator();
$this->assertEquals(
$expected,
$exception->trans($translator),
$message
);
}
}
Common Patterns
-
Test Exception Factory Methods
public function testExceptionFactory(): void { $exception = ValidationExceptionFactory::required('email'); $this->assertInstanceOf(ValidationException::class, $exception); $this->assertEquals( 'The field email is required.', $exception->getMessage() ); }
-
Test Translation Fallbacks
public function testFallbackTranslation(): void { $translator = new Translator( $this->provider, 'fr', ['es', 'en'] ); $exception = new ValidationException('test.message'); // Should fallback to English. $this->assertEquals( 'Test message', $exception->trans($translator) ); }
-
Test ICU Edge Cases
public function testComplexIcuPattern(): void { $exception = new ValidationException( '{count, plural, =0{Empty} one{# item} other{# items}}', ['count' => 0] ); $this->assertEquals('Empty', $exception->getMessage()); }
Remember:
- Always test both translated and untranslated scenarios.
- Test parameter substitution.
- Verify ICU message formatting.
- Check translation consistency across locales.
- Test error handling.
- Use data providers for multiple test cases.