matthiasnoback/symfony-config-test
PHPUnit helpers for testing Symfony Config definitions. Provides a test case trait and assertions to validate config trees, ensuring required nodes, types, and constraints behave as expected by asserting valid and invalid configuration arrays.
Installation:
composer require --dev matthiasnoback/symfony-config-test
Add to your composer.json under require-dev to ensure it’s only installed for testing.
Basic Test Structure:
Create a test class extending PHPUnit\Framework\TestCase and include the trait:
use Matthias\SymfonyConfigTest\PhpUnit\ConfigurationTestCaseTrait;
class MyConfigTest extends TestCase
{
use ConfigurationTestCaseTrait;
protected function getConfiguration(): ConfigurationInterface
{
return new MyAppConfiguration(); // Your Symfony Config Component class
}
}
First Use Case: Test invalid configuration with missing required values:
public function test_missing_required_value()
{
$this->assertConfigurationIsInvalid([[]], 'required_value');
}
Testing Configuration Validation:
assertConfigurationIsInvalid() for invalid inputs (e.g., missing required fields, wrong types).public function test_invalid_type()
{
$this->assertConfigurationIsInvalid(
[['invalid_key' => 123]], // Wrong type (should be string)
'Expected a string'
);
}
Testing Processed Configuration:
assertProcessedConfigurationEquals() to verify merged/processed config matches expectations.public function test_processed_config()
{
$this->assertProcessedConfigurationEquals(
[
['app' => ['debug' => true]],
['app' => ['debug' => false]]
],
['app' => ['debug' => false]] // Final merged state
);
}
Partial Tree Testing:
$breadcrumbPath (e.g., doctrine.orm).public function test_doctrine_orm_only()
{
$this->assertProcessedConfigurationEquals(
[['doctrine' => ['orm' => ['entities' => ['App\Entity\User']]]]],
['doctrine' => ['orm' => ['entities' => ['App\Entity\User']]]],
'doctrine.orm'
);
}
Prototyped Nodes:
array_node.*) with wildcards:
public function test_prototyped_defaults()
{
$this->assertProcessedConfigurationEquals(
[['services' => ['*' => ['autowire' => true]]]],
['services' => ['default' => ['autowire' => true]]],
'services.*.autowire'
);
}
Bundle Configuration:
config/your-package.php) by mocking the config loader or using the package’s Configuration class directly.protected function getConfiguration(): ConfigurationInterface
{
return new YourPackageConfiguration();
}
Environment-Aware Tests:
assertConfigurationIsInvalid() to validate .env or config file inputs:
public function test_env_validation()
{
$this->assertConfigurationIsInvalid(
[['APP_DEBUG' => 'invalid']],
'must be a boolean'
);
}
Service Provider Integration:
register() methods) by injecting the config class into the test:
public function test_service_provider_config()
{
$provider = new YourServiceProvider(app());
$config = $provider->getConfig(); // Hypothetical method
$this->assertProcessedConfigurationEquals([...], $config);
}
Breadcrumb Path Syntax:
array_node/child (slashes) instead of array_node.child (dots).database.connections.mysql).* for prototyped nodes (e.g., services.*.autowire).Merging Logic:
assertProcessedConfigurationEquals() expects an array of arrays to simulate merging.// Correct:
$this->assertProcessedConfigurationEquals([['key' => 'value']], ['key' => 'value']);
Partial Testing Quirks:
doctrine.orm) but providing config for unrelated branches (e.g., doctrine.dbal).Exception Messages:
assertConfigurationIsInvalid():
$this->assertConfigurationIsInvalid([[]], '/required.*value/');
Inspect Processed Config:
ConfigCache or Processor directly to debug:
$processor = new Processor();
$processed = $processor->processConfiguration(
$this->getConfiguration(),
[['raw' => 'input']]
);
dd($processed); // Debug output
PHPUnit Debugging:
$this->assertProcessedConfigurationEquals([...], [...], null, true); // Verbose mode
CI/CD Integration:
--debug to catch flaky assertions:
phpunit --debug
Custom Assertions:
trait CustomConfigTestTrait
{
protected function assertCustomConfig(array $input, array $expected)
{
$this->assertProcessedConfigurationEquals([$input], $expected);
// Add custom logic (e.g., validate against a schema)
}
}
Integration with Laravel’s Config:
getConfiguration() to load Laravel’s config:
protected function getConfiguration(): ConfigurationInterface
{
return new class extends Configuration {
public function getConfigTreeBuilder()
{
return (new TreeBuilder())
->root('app')
->children()
->scalarNode('timezone')->defaultValue(config('app.timezone'))->end()
->end();
}
};
}
Mocking Dependencies:
public function test_database_config()
{
$this->mockDatabaseConnection();
$this->assertProcessedConfigurationEquals([...], [...], 'database');
}
private $processedConfigCache = [];
protected function getProcessedConfig(array $input)
{
$cacheKey = md5(serialize($input));
return $this->processedConfigCache[$cacheKey] ?? (
$this->processedConfigCache[$cacheKey] = $this->processConfig($input)
);
}
Config Publishing:
php artisan vendor:publish) are tested:
public function test_published_config()
{
$this->publishConfig();
$this->assertProcessedConfigurationEquals([...], config('published-key'));
}
Environment Variables:
.env overrides by mocking env():
protected function setUp(): void
{
$this->mockEnv(['APP_DEBUG' => 'true']);
}
Package Configs:
config/your-package.php), ensure tests reflect all possible states:
public function test_all_package_config_states()
{
$this->assertProcessedConfigurationEquals([...], [...], 'your-package');
$this->assertProcessedConfigurationEquals([...], [...], 'your-package.other-section');
}
How can I help you explore Laravel packages today?