diff --git a/docs/en/02_Developer_Guides/04_Configuration/00_Configuration.md b/docs/en/02_Developer_Guides/04_Configuration/00_Configuration.md index 3ddd11d95..b0febfc70 100644 --- a/docs/en/02_Developer_Guides/04_Configuration/00_Configuration.md +++ b/docs/en/02_Developer_Guides/04_Configuration/00_Configuration.md @@ -377,6 +377,48 @@ When you have more than one rule for a nested fragment, they're joined like That is, the fragment will be included if all Only rules match, except if all Except rules match. +## Unit tests + +Sometimes, it's necessary to change a configuration value in your unit tests. +One way to do this is to use the `withConfig` method. +This is especially handy when using data providers. +Example below shows one unit test using a data provider. +This unit test changes configuration before testing functionality. +The test will run three times, each run with different configuration value. +Note that the configuration change is active only within the callback function. + +```php +/** + * @dataProvider testValuesProvider + * @param string $value + * @param string $expected + */ +public function testConfigValues($value, $expected) +{ + $result = Config::withConfig(function(MutableConfigCollectionInterface $config) use ($value) { + // update your config + $config->set(MyService::class, 'some_setting', $value); + + // your test code goes here and it runs with your changed config + return MyService::singleton()->executeSomeFunction(); + }); + + // your config change no longer applies here as it's outside of callback + + // assertions can be done here but also inside the callback function + $this->assertEquals($expected, $result); +} + +public function testValuesProvider(): array +{ + return [ + ['test value 1', 'expected value 1'], + ['test value 2', 'expected value 2'], + ['test value 3', 'expected value 3'], + ]; +} +``` + ## API Documentation * [Config](api:SilverStripe\Core\Config\Config) diff --git a/src/Core/Config/Config.php b/src/Core/Config/Config.php index e8d5a960f..89aab2a32 100644 --- a/src/Core/Config/Config.php +++ b/src/Core/Config/Config.php @@ -4,7 +4,6 @@ namespace SilverStripe\Core\Config; use InvalidArgumentException; use SilverStripe\Config\Collections\ConfigCollectionInterface; -use SilverStripe\Config\Collections\DeltaConfigCollection; use SilverStripe\Config\Collections\MutableConfigCollectionInterface; abstract class Config @@ -119,4 +118,23 @@ abstract class Config { return new Config_ForClass($class); } + + /** + * Perform the given operation in an isolated config state. + * On return, the config state will be restored, so any modifications are temporary. + * + * @param callable $callback Callback to run. Will be passed the nested config state as a parameter + * @return mixed Result of callback + */ + public static function withConfig($callback) + { + static::nest(); + $config = static::modify(); + + try { + return $callback($config); + } finally { + static::unnest(); + } + } } diff --git a/tests/php/Core/Config/ConfigTest.php b/tests/php/Core/Config/ConfigTest.php index 38409ad52..36c13653c 100644 --- a/tests/php/Core/Config/ConfigTest.php +++ b/tests/php/Core/Config/ConfigTest.php @@ -2,6 +2,7 @@ namespace SilverStripe\Core\Tests\Config; +use SilverStripe\Config\Collections\MutableConfigCollectionInterface; use SilverStripe\Config\MergeStrategy\Priority; use SilverStripe\Core\Config\Config; use SilverStripe\Dev\SapphireTest; @@ -322,4 +323,44 @@ class ConfigTest extends SapphireTest $this->assertTrue(empty($config->bar)); $this->assertNull($config->bar); } + + public function testWithConfig() + { + $oldValue = 'test_1'; + $newValue1 = 'new value 1'; + $newValue2 = 'new value 2'; + $property = 'third'; + + $this->assertEquals( + $oldValue, + Config::inst()->get(ConfigTest\First::class, $property) + ); + + Config::withConfig(function (MutableConfigCollectionInterface $config) use ($newValue1, $newValue2, $property) { + $config->set(ConfigTest\First::class, $property, $newValue1); + + $this->assertEquals( + $newValue1, + Config::inst()->get(ConfigTest\First::class, $property) + ); + + $resultValue = Config::withConfig(function (MutableConfigCollectionInterface $config) use ($newValue2, $property) { + $config->set(ConfigTest\First::class, $property, $newValue2); + + return Config::inst()->get(ConfigTest\First::class, $property); + }); + + $this->assertEquals($newValue2, $resultValue); + + $this->assertEquals( + $newValue1, + Config::inst()->get(ConfigTest\First::class, $property) + ); + }); + + $this->assertEquals( + $oldValue, + Config::inst()->get(ConfigTest\First::class, $property) + ); + } }