mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #3058 from tractorcow/pulls/injector-stack-tests
API Injector supports nesting
This commit is contained in:
commit
bbd7bba11f
@ -210,6 +210,13 @@ class Injector {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The injector instance this one was copied from when Injector::nest() was called.
|
||||
*
|
||||
* @var Injector
|
||||
*/
|
||||
protected $nestedFrom = null;
|
||||
|
||||
/**
|
||||
* If a user wants to use the injector as a static reference
|
||||
*
|
||||
@ -227,9 +234,38 @@ class Injector {
|
||||
* Sets the default global injector instance.
|
||||
*
|
||||
* @param Injector $instance
|
||||
* @return Injector Reference to new active Injector instance
|
||||
*/
|
||||
public static function set_inst(Injector $instance) {
|
||||
self::$instance = $instance;
|
||||
return self::$instance = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the newly active {@link Injector} be a copy of the current active
|
||||
* {@link Injector} instance.
|
||||
*
|
||||
* You can then make changes to the injector with methods such as
|
||||
* {@link Injector::inst()->registerService()} which will be discarded
|
||||
* upon a subsequent call to {@link Injector::unnest()}
|
||||
*
|
||||
* @return Injector Reference to new active Injector instance
|
||||
*/
|
||||
public static function nest() {
|
||||
$current = self::$instance;
|
||||
|
||||
$new = clone $current;
|
||||
$new->nestedFrom = $current;
|
||||
return self::set_inst($new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the active Injector back to the Injector instance the current active
|
||||
* Injector object was copied from.
|
||||
*
|
||||
* @return Injector Reference to restored active Injector instance
|
||||
*/
|
||||
public static function unnest() {
|
||||
return self::set_inst(self::$instance->nestedFrom);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,13 +201,15 @@ class Config {
|
||||
* A use case for replacing the active configuration set would be for
|
||||
* creating an isolated environment for unit tests.
|
||||
*
|
||||
* @return Config
|
||||
* @param Config $instance New instance of Config to assign
|
||||
* @return Config Reference to new active Config instance
|
||||
*/
|
||||
public static function set_instance($instance) {
|
||||
self::$instance = $instance;
|
||||
|
||||
global $_SINGLETONS;
|
||||
$_SINGLETONS['Config'] = $instance;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,23 +217,27 @@ class Config {
|
||||
* {@link Config} instance.
|
||||
*
|
||||
* You can then make changes to the configuration by calling update and
|
||||
* remove on the new value returned by Config::inst(), and then discard
|
||||
* remove on the new value returned by {@link Config::inst()}, and then discard
|
||||
* those changes later by calling unnest.
|
||||
*
|
||||
* @return Config Reference to new active Config instance
|
||||
*/
|
||||
public static function nest() {
|
||||
$current = self::$instance;
|
||||
|
||||
$new = clone $current;
|
||||
$new->nestedFrom = $current;
|
||||
self::set_instance($new);
|
||||
return self::set_instance($new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the active Config back to the Config instance the current active
|
||||
* Config object was copied from.
|
||||
*
|
||||
* @return Config Reference to new active Config instance
|
||||
*/
|
||||
public static function unnest() {
|
||||
self::set_instance(self::$instance->nestedFrom);
|
||||
return self::set_instance(self::$instance->nestedFrom);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,3 +7,5 @@
|
||||
user login name between sessions, and disable browser auto-completion on the username field.
|
||||
Note that users of certain browsers who have previously autofilled and saved login credentials
|
||||
will need to clear their password autofill history before this setting is properly respected.
|
||||
* Test cases that rely on updating and restoring `[api:Injector]` services may now take advantage
|
||||
of the new `Injector::nest()` and `Injector::unnest()` methods to sandbox their alterations.
|
@ -192,6 +192,30 @@ would
|
||||
* Create a MySQLDatabase class, passing dbusername and dbpassword as the
|
||||
parameters to the constructor
|
||||
|
||||
### Testing with Injector in a sandbox environment
|
||||
|
||||
In situations where injector states must be temporarily overridden, it is possible
|
||||
to create nested Injector instances which may be later discarded, reverting the
|
||||
application to the original state.
|
||||
|
||||
This is useful when writing test cases, as certain services may be necessary to
|
||||
override for a single method call.
|
||||
|
||||
For instance, a temporary service can be registered and unregistered as below:
|
||||
|
||||
:::php
|
||||
// Setup default service
|
||||
Injector::inst()->registerService(new LiveService(), 'ServiceName');
|
||||
|
||||
// Test substitute service temporarily
|
||||
Injector::nest();
|
||||
Injector::inst()->registerService(new TestingService(), 'ServiceName');
|
||||
$service = Injector::inst()->get('ServiceName');
|
||||
// ... do something with $service
|
||||
Injector::unnest();
|
||||
|
||||
// ... future requests for 'ServiceName' will return the LiveService instance
|
||||
|
||||
|
||||
### What are Services?
|
||||
|
||||
|
@ -14,6 +14,9 @@ class DirectorTest extends SapphireTest {
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Required for testRequestFilterInDirectorTest
|
||||
Injector::nest();
|
||||
|
||||
// Hold the original request URI once so it doesn't get overwritten
|
||||
if(!self::$originalRequestURI) {
|
||||
self::$originalRequestURI = $_SERVER['REQUEST_URI'];
|
||||
@ -42,7 +45,7 @@ class DirectorTest extends SapphireTest {
|
||||
// TODO Remove director rule, currently API doesnt allow this
|
||||
|
||||
// Remove base URL override (setting to false reverts to default behaviour)
|
||||
Director::setBaseURL(false);
|
||||
Config::inst()->update('Director', 'alternate_base_url', false);
|
||||
|
||||
// Reinstate the original REQUEST_URI after it was modified by some tests
|
||||
$_SERVER['REQUEST_URI'] = self::$originalRequestURI;
|
||||
@ -53,6 +56,8 @@ class DirectorTest extends SapphireTest {
|
||||
}
|
||||
}
|
||||
|
||||
Injector::unnest();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
@ -80,7 +85,7 @@ class DirectorTest extends SapphireTest {
|
||||
|
||||
$rootURL = Director::protocolAndHost();
|
||||
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
|
||||
Director::setBaseURL('/mysite/');
|
||||
Config::inst()->update('Director', 'alternate_base_url', '/mysite/');
|
||||
|
||||
// Test already absolute url
|
||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL));
|
||||
@ -387,8 +392,6 @@ class DirectorTest extends SapphireTest {
|
||||
|
||||
$processor = new RequestProcessor(array($filter));
|
||||
|
||||
$currentProcessor = Injector::inst()->get('RequestProcessor');
|
||||
|
||||
Injector::inst()->registerService($processor, 'RequestProcessor');
|
||||
|
||||
$response = Director::test('some-dummy-url');
|
||||
@ -413,9 +416,6 @@ class DirectorTest extends SapphireTest {
|
||||
|
||||
// preCall 'false' will trigger an exception and prevent post call execution
|
||||
$this->assertEquals(2, $filter->postCalls);
|
||||
|
||||
// swap back otherwise our wrapping test execution request may fail in the post processing later
|
||||
Injector::inst()->registerService($currentProcessor, 'RequestProcessor');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,6 +76,28 @@ class ConfigStaticTest_Combined3 extends ConfigStaticTest_Combined2 {
|
||||
|
||||
class ConfigTest extends SapphireTest {
|
||||
|
||||
public function testNest() {
|
||||
|
||||
// Check basic config
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'foo'));
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'bar'));
|
||||
|
||||
// Test nest copies data
|
||||
Config::nest();
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'foo'));
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'bar'));
|
||||
|
||||
// Test nested data can be updated
|
||||
Config::inst()->update('ConfigTest_DefinesFooAndBar', 'foo', 4);
|
||||
$this->assertEquals(4, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'foo'));
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'bar'));
|
||||
|
||||
// Test unnest restores data
|
||||
Config::unnest();
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'foo'));
|
||||
$this->assertEquals(3, Config::inst()->get('ConfigTest_DefinesFooAndBar', 'bar'));
|
||||
}
|
||||
|
||||
public function testUpdateStatic() {
|
||||
$this->assertEquals(Config::inst()->get('ConfigStaticTest_First', 'first', Config::FIRST_SET),
|
||||
array('test_1'));
|
||||
|
@ -12,6 +12,24 @@ define('TEST_SERVICES', dirname(__FILE__) . '/testservices');
|
||||
*/
|
||||
class InjectorTest extends SapphireTest {
|
||||
|
||||
protected $nestingLevel = 0;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->nestingLevel = 0;
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
|
||||
while($this->nestingLevel > 0) {
|
||||
$this->nestingLevel--;
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testCorrectlyInitialised() {
|
||||
$injector = Injector::inst();
|
||||
$this->assertTrue($injector->getConfigLocator() instanceof SilverStripeServiceConfigurationLocator,
|
||||
@ -563,6 +581,64 @@ class InjectorTest extends SapphireTest {
|
||||
$this->assertInstanceOf('TestObject', $injector->get('service'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test nesting of injector
|
||||
*/
|
||||
public function testNest() {
|
||||
|
||||
// Outer nest to avoid interference with other
|
||||
Injector::nest();
|
||||
$this->nestingLevel++;
|
||||
|
||||
// Test services
|
||||
$config = array(
|
||||
'NewRequirementsBackend',
|
||||
);
|
||||
Injector::inst()->load($config);
|
||||
$si = Injector::inst()->get('TestStaticInjections');
|
||||
$this->assertInstanceOf('TestStaticInjections', $si);
|
||||
$this->assertInstanceOf('NewRequirementsBackend', $si->backend);
|
||||
$this->assertInstanceOf('MyParentClass', Injector::inst()->get('MyParentClass'));
|
||||
$this->assertInstanceOf('MyChildClass', Injector::inst()->get('MyChildClass'));
|
||||
|
||||
// Test that nested injector values can be overridden
|
||||
Injector::nest();
|
||||
$this->nestingLevel++;
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
$newsi = Injector::inst()->get('TestStaticInjections');
|
||||
$newsi->backend = new OriginalRequirementsBackend();
|
||||
Injector::inst()->registerService($newsi, 'TestStaticInjections');
|
||||
Injector::inst()->registerService(new MyChildClass(), 'MyParentClass');
|
||||
|
||||
// Check that these overridden values are retrievable
|
||||
$si = Injector::inst()->get('TestStaticInjections');
|
||||
$this->assertInstanceOf('TestStaticInjections', $si);
|
||||
$this->assertInstanceOf('OriginalRequirementsBackend', $si->backend);
|
||||
$this->assertInstanceOf('MyParentClass', Injector::inst()->get('MyParentClass'));
|
||||
$this->assertInstanceOf('MyParentClass', Injector::inst()->get('MyChildClass'));
|
||||
|
||||
// Test that unnesting restores expected behaviour
|
||||
Injector::unnest();
|
||||
$this->nestingLevel--;
|
||||
$si = Injector::inst()->get('TestStaticInjections');
|
||||
$this->assertInstanceOf('TestStaticInjections', $si);
|
||||
$this->assertInstanceOf('NewRequirementsBackend', $si->backend);
|
||||
$this->assertInstanceOf('MyParentClass', Injector::inst()->get('MyParentClass'));
|
||||
$this->assertInstanceOf('MyChildClass', Injector::inst()->get('MyChildClass'));
|
||||
|
||||
// Test reset of cache
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
$si = Injector::inst()->get('TestStaticInjections');
|
||||
$this->assertInstanceOf('TestStaticInjections', $si);
|
||||
$this->assertInstanceOf('NewRequirementsBackend', $si->backend);
|
||||
$this->assertInstanceOf('MyParentClass', Injector::inst()->get('MyParentClass'));
|
||||
$this->assertInstanceOf('MyChildClass', Injector::inst()->get('MyChildClass'));
|
||||
|
||||
// Return to nestingLevel 0
|
||||
Injector::unnest();
|
||||
$this->nestingLevel--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
|
||||
|
Loading…
Reference in New Issue
Block a user