3 use Wikimedia\Services\ServiceContainer
;
6 * @covers Wikimedia\Services\ServiceContainer
8 class ServiceContainerTest
extends PHPUnit\Framework\TestCase
{
10 use MediaWikiCoversValidator
; // TODO this library is supposed to be independent of MediaWiki
11 use PHPUnit4And6Compat
;
13 private function newServiceContainer( $extraArgs = [] ) {
14 return new ServiceContainer( $extraArgs );
17 public function testGetServiceNames() {
18 $services = $this->newServiceContainer();
19 $names = $services->getServiceNames();
21 $this->assertInternalType( 'array', $names );
22 $this->assertEmpty( $names );
24 $name = 'TestService92834576';
25 $services->defineService( $name, function () {
29 $names = $services->getServiceNames();
30 $this->assertContains( $name, $names );
33 public function testHasService() {
34 $services = $this->newServiceContainer();
36 $name = 'TestService92834576';
37 $this->assertFalse( $services->hasService( $name ) );
39 $services->defineService( $name, function () {
43 $this->assertTrue( $services->hasService( $name ) );
46 public function testGetService() {
47 $services = $this->newServiceContainer( [ 'Foo' ] );
49 $theService = new stdClass();
50 $name = 'TestService92834576';
53 $services->defineService(
55 function ( $actualLocator, $extra ) use ( $services, $theService, &$count ) {
57 PHPUnit_Framework_Assert
::assertSame( $services, $actualLocator );
58 PHPUnit_Framework_Assert
::assertSame( $extra, 'Foo' );
63 $this->assertSame( $theService, $services->getService( $name ) );
65 $services->getService( $name );
66 $this->assertSame( 1, $count, 'instantiator should be called exactly once!' );
69 public function testGetServiceRecursionCheck() {
70 $services = $this->newServiceContainer();
72 $services->defineService( 'service1', function ( ServiceContainer
$services ) {
73 $services->getService( 'service2' );
76 $services->defineService( 'service2', function ( ServiceContainer
$services ) {
77 $services->getService( 'service3' );
80 $services->defineService( 'service3', function ( ServiceContainer
$services ) {
81 $services->getService( 'service1' );
84 $exceptionThrown = false;
86 $services->getService( 'service1' );
87 } catch ( RuntimeException
$e ) {
88 $exceptionThrown = true;
89 $this->assertSame( 'Circular dependency when creating service! ' .
90 'service1 -> service2 -> service3 -> service1', $e->getMessage() );
92 $this->assertTrue( $exceptionThrown, 'RuntimeException must be thrown' );
95 public function testGetService_fail_unknown() {
96 $services = $this->newServiceContainer();
98 $name = 'TestService92834576';
100 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException
::class );
102 $services->getService( $name );
105 public function testPeekService() {
106 $services = $this->newServiceContainer();
108 $services->defineService(
111 return new stdClass();
115 $services->defineService(
118 return new stdClass();
122 // trigger instantiation of Foo
123 $services->getService( 'Foo' );
125 $this->assertInternalType(
127 $services->peekService( 'Foo' ),
128 'Peek should return the service object if it had been accessed before.'
132 $services->peekService( 'Bar' ),
133 'Peek should return null if the service was never accessed.'
137 public function testPeekService_fail_unknown() {
138 $services = $this->newServiceContainer();
140 $name = 'TestService92834576';
142 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException
::class );
144 $services->peekService( $name );
147 public function testDefineService() {
148 $services = $this->newServiceContainer();
150 $theService = new stdClass();
151 $name = 'TestService92834576';
153 $services->defineService( $name, function ( $actualLocator ) use ( $services, $theService ) {
154 PHPUnit_Framework_Assert
::assertSame( $services, $actualLocator );
158 $this->assertTrue( $services->hasService( $name ) );
159 $this->assertSame( $theService, $services->getService( $name ) );
162 public function testDefineService_fail_duplicate() {
163 $services = $this->newServiceContainer();
165 $theService = new stdClass();
166 $name = 'TestService92834576';
168 $services->defineService( $name, function () use ( $theService ) {
172 $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException
::class );
174 $services->defineService( $name, function () use ( $theService ) {
179 public function testApplyWiring() {
180 $services = $this->newServiceContainer();
183 'Foo' => function () {
186 'Bar' => function () {
191 $services->applyWiring( $wiring );
193 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
194 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
197 public function testImportWiring() {
198 $services = $this->newServiceContainer();
201 'Foo' => function () {
204 'Bar' => function () {
207 'Car' => function () {
212 $services->applyWiring( $wiring );
214 $services->addServiceManipulator( 'Foo', function ( $service ) {
215 return $service . '+X';
218 $services->addServiceManipulator( 'Car', function ( $service ) {
219 return $service . '+X';
222 $newServices = $this->newServiceContainer();
224 // create a service with manipulator
225 $newServices->defineService( 'Foo', function () {
229 $newServices->addServiceManipulator( 'Foo', function ( $service ) {
230 return $service . '+Y';
233 // create a service before importing, so we can later check that
234 // existing service instances survive importWiring()
235 $newServices->defineService( 'Car', function () {
239 // force instantiation
240 $newServices->getService( 'Car' );
242 // Define another service, so we can later check that extra wiring
244 $newServices->defineService( 'Xar', function () {
248 // import wiring, but skip `Bar`
249 $newServices->importWiring( $services, [ 'Bar' ] );
251 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
252 $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
254 // import all wiring, but preserve existing service instance
255 $newServices->importWiring( $services );
257 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
258 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
259 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
260 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
263 public function testLoadWiringFiles() {
264 $services = $this->newServiceContainer();
267 __DIR__
. '/TestWiring1.php',
268 __DIR__
. '/TestWiring2.php',
271 $services->loadWiringFiles( $wiringFiles );
273 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
274 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
277 public function testLoadWiringFiles_fail_duplicate() {
278 $services = $this->newServiceContainer();
281 __DIR__
. '/TestWiring1.php',
282 __DIR__
. '/./TestWiring1.php',
285 // loading the same file twice should fail, because
286 $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException
::class );
288 $services->loadWiringFiles( $wiringFiles );
291 public function testRedefineService() {
292 $services = $this->newServiceContainer( [ 'Foo' ] );
294 $theService1 = new stdClass();
295 $name = 'TestService92834576';
297 $services->defineService( $name, function () {
298 PHPUnit_Framework_Assert
::fail(
299 'The original instantiator function should not get called'
303 // redefine before instantiation
304 $services->redefineService(
306 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
307 PHPUnit_Framework_Assert
::assertSame( $services, $actualLocator );
308 PHPUnit_Framework_Assert
::assertSame( 'Foo', $extra );
313 // force instantiation, check result
314 $this->assertSame( $theService1, $services->getService( $name ) );
317 public function testRedefineService_disabled() {
318 $services = $this->newServiceContainer( [ 'Foo' ] );
320 $theService1 = new stdClass();
321 $name = 'TestService92834576';
323 $services->defineService( $name, function () {
327 // disable the service. we should be able to redefine it anyway.
328 $services->disableService( $name );
330 $services->redefineService( $name, function () use ( $theService1 ) {
334 // force instantiation, check result
335 $this->assertSame( $theService1, $services->getService( $name ) );
338 public function testRedefineService_fail_undefined() {
339 $services = $this->newServiceContainer();
341 $theService = new stdClass();
342 $name = 'TestService92834576';
344 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException
::class );
346 $services->redefineService( $name, function () use ( $theService ) {
351 public function testRedefineService_fail_in_use() {
352 $services = $this->newServiceContainer( [ 'Foo' ] );
354 $theService = new stdClass();
355 $name = 'TestService92834576';
357 $services->defineService( $name, function () {
361 // create the service, so it can no longer be redefined
362 $services->getService( $name );
364 $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException
::class );
366 $services->redefineService( $name, function () use ( $theService ) {
371 public function testAddServiceManipulator() {
372 $services = $this->newServiceContainer( [ 'Foo' ] );
374 $theService1 = new stdClass();
375 $theService2 = new stdClass();
376 $name = 'TestService92834576';
378 $services->defineService(
380 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
381 PHPUnit_Framework_Assert
::assertSame( $services, $actualLocator );
382 PHPUnit_Framework_Assert
::assertSame( 'Foo', $extra );
387 $services->addServiceManipulator(
390 $theService, $actualLocator, $extra
392 $services, $theService1, $theService2
394 PHPUnit_Framework_Assert
::assertSame( $theService1, $theService );
395 PHPUnit_Framework_Assert
::assertSame( $services, $actualLocator );
396 PHPUnit_Framework_Assert
::assertSame( 'Foo', $extra );
401 // force instantiation, check result
402 $this->assertSame( $theService2, $services->getService( $name ) );
405 public function testAddServiceManipulator_fail_undefined() {
406 $services = $this->newServiceContainer();
408 $theService = new stdClass();
409 $name = 'TestService92834576';
411 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException
::class );
413 $services->addServiceManipulator( $name, function () use ( $theService ) {
418 public function testAddServiceManipulator_fail_in_use() {
419 $services = $this->newServiceContainer( [ 'Foo' ] );
421 $theService = new stdClass();
422 $name = 'TestService92834576';
424 $services->defineService( $name, function () use ( $theService ) {
428 // create the service, so it can no longer be redefined
429 $services->getService( $name );
431 $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException
::class );
433 $services->addServiceManipulator( $name, function () {
438 public function testDisableService() {
439 $services = $this->newServiceContainer( [ 'Foo' ] );
441 $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService
::class )
443 $destructible->expects( $this->once() )
444 ->method( 'destroy' );
446 $services->defineService( 'Foo', function () use ( $destructible ) {
447 return $destructible;
449 $services->defineService( 'Bar', function () {
450 return new stdClass();
452 $services->defineService( 'Qux', function () {
453 return new stdClass();
456 // instantiate Foo and Bar services
457 $services->getService( 'Foo' );
458 $services->getService( 'Bar' );
460 // disable service, should call destroy() once.
461 $services->disableService( 'Foo' );
463 // disabled service should still be listed
464 $this->assertContains( 'Foo', $services->getServiceNames() );
466 // getting other services should still work
467 $services->getService( 'Bar' );
469 // disable non-destructible service, and not-yet-instantiated service
470 $services->disableService( 'Bar' );
471 $services->disableService( 'Qux' );
473 $this->assertNull( $services->peekService( 'Bar' ) );
474 $this->assertNull( $services->peekService( 'Qux' ) );
476 // disabled service should still be listed
477 $this->assertContains( 'Bar', $services->getServiceNames() );
478 $this->assertContains( 'Qux', $services->getServiceNames() );
480 $this->setExpectedException( Wikimedia\Services\ServiceDisabledException
::class );
481 $services->getService( 'Qux' );
484 public function testDisableService_fail_undefined() {
485 $services = $this->newServiceContainer();
487 $theService = new stdClass();
488 $name = 'TestService92834576';
490 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException
::class );
492 $services->redefineService( $name, function () use ( $theService ) {
497 public function testDestroy() {
498 $services = $this->newServiceContainer();
500 $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService
::class )
502 $destructible->expects( $this->once() )
503 ->method( 'destroy' );
505 $services->defineService( 'Foo', function () use ( $destructible ) {
506 return $destructible;
509 $services->defineService( 'Bar', function () {
510 return new stdClass();
513 // create the service
514 $services->getService( 'Foo' );
516 // destroy the container
517 $services->destroy();
519 $this->setExpectedException( Wikimedia\Services\ContainerDisabledException
::class );
520 $services->getService( 'Bar' );