service-container.spec.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. import {expect} from 'chai';
  2. import {Service} from './service.js';
  3. import {format} from '@e22m4u/js-format';
  4. import {ServiceContainer} from './service-container.js';
  5. import {SERVICE_CONTAINER_CLASS_NAME} from './service-container.js';
  6. describe('ServiceContainer', function () {
  7. it('exposes static property "kinds"', function () {
  8. expect(ServiceContainer.kinds).to.be.eql([SERVICE_CONTAINER_CLASS_NAME]);
  9. const MyContainer = class extends ServiceContainer {};
  10. expect(MyContainer.kinds).to.be.eql([SERVICE_CONTAINER_CLASS_NAME]);
  11. });
  12. describe('constructor', function () {
  13. it('does not require any arguments', function () {
  14. const res = new ServiceContainer();
  15. expect(res).to.be.instanceof(ServiceContainer);
  16. });
  17. it('sets the first argument as the parent container', function () {
  18. const parent = new ServiceContainer();
  19. const container = new ServiceContainer(parent);
  20. const res = container['_parent'];
  21. expect(res).to.be.eq(parent);
  22. });
  23. it('requires the first argument to be an instance of ServiceContainer', function () {
  24. const throwable = v => () => new ServiceContainer(v);
  25. const error = v =>
  26. format(
  27. 'The provided parameter "parent" of ServicesContainer.constructor ' +
  28. 'must be an instance ServiceContainer, but %s given.',
  29. v,
  30. );
  31. expect(throwable('str')).to.throw(error('"str"'));
  32. expect(throwable('')).to.throw(error('""'));
  33. expect(throwable(10)).to.throw(error('10'));
  34. expect(throwable(0)).to.throw(error('0'));
  35. expect(throwable(true)).to.throw(error('true'));
  36. expect(throwable(false)).to.throw(error('false'));
  37. expect(throwable([])).to.throw(error('Array'));
  38. expect(throwable({})).to.throw(error('Object'));
  39. throwable(undefined)();
  40. throwable(null)();
  41. throwable(new ServiceContainer())();
  42. });
  43. });
  44. describe('getParent', function () {
  45. it('throws an error if no parent container', function () {
  46. const container = new ServiceContainer();
  47. const throwable = () => container.getParent();
  48. expect(throwable).to.throw('The service container has no parent.');
  49. });
  50. it('returns parent container', function () {
  51. const parent = new ServiceContainer();
  52. const container = new ServiceContainer(parent);
  53. const res = container.getParent();
  54. expect(res).to.be.eq(parent);
  55. });
  56. });
  57. describe('hasParent', function () {
  58. it('returns true if a parent container exists and false otherwise', function () {
  59. const container1 = new ServiceContainer();
  60. const parent = new ServiceContainer();
  61. const container2 = new ServiceContainer(parent);
  62. expect(container1.hasParent()).to.be.false;
  63. expect(container2.hasParent()).to.be.true;
  64. });
  65. });
  66. describe('get', function () {
  67. it('throws an error if no constructor given', function () {
  68. const container = new ServiceContainer();
  69. const throwable = v => () => container.get(v);
  70. const error = v =>
  71. format(
  72. 'The first argument of ServicesContainer.get must be ' +
  73. 'a class constructor, but %s given.',
  74. v,
  75. );
  76. expect(throwable('str')).to.throw(error('"str"'));
  77. expect(throwable(10)).to.throw(error('10'));
  78. expect(throwable(true)).to.throw(error('true'));
  79. expect(throwable(false)).to.throw(error('false'));
  80. expect(throwable(undefined)).to.throw(error('undefined'));
  81. expect(throwable(null)).to.throw(error('null'));
  82. expect(throwable([])).to.throw(error('Array'));
  83. expect(throwable({})).to.throw(error('Object'));
  84. });
  85. describe('Service', function () {
  86. it('passes itself and given arguments to the given constructor', function () {
  87. let executed = 0;
  88. let givenContainer;
  89. let givenArgs;
  90. class MyService extends Service {
  91. constructor(container, ...args) {
  92. super(container);
  93. executed++;
  94. givenContainer = container;
  95. givenArgs = args;
  96. }
  97. }
  98. const container = new ServiceContainer();
  99. const service = container.get(MyService, 'foo', 'bar');
  100. expect(service).to.be.instanceof(MyService);
  101. expect(executed).to.be.eq(1);
  102. expect(givenContainer).to.be.eq(container);
  103. expect(givenArgs).to.be.eql(['foo', 'bar']);
  104. });
  105. it('instantiates from an existing factory function', function () {
  106. let executed = 0;
  107. let givenContainer;
  108. let givenArgs;
  109. class MyService extends Service {
  110. constructor(container, ...args) {
  111. super(container);
  112. executed++;
  113. givenContainer = container;
  114. givenArgs = args;
  115. }
  116. }
  117. const container = new ServiceContainer();
  118. container.add(MyService, 'foo', 'bar');
  119. expect(executed).to.be.eq(0);
  120. const service = container.get(MyService);
  121. expect(service).to.be.instanceof(MyService);
  122. expect(executed).to.be.eq(1);
  123. expect(givenContainer).to.be.eq(container);
  124. expect(givenArgs).to.be.eql(['foo', 'bar']);
  125. });
  126. it('overrides an existing factory function', function () {
  127. let executed = 0;
  128. let givenContainer;
  129. let givenArgs;
  130. class MyService extends Service {
  131. constructor(container, ...args) {
  132. super(container);
  133. executed++;
  134. givenContainer = container;
  135. givenArgs = args;
  136. }
  137. }
  138. const container = new ServiceContainer();
  139. container.add(MyService, 'foo', 'bar');
  140. expect(executed).to.be.eq(0);
  141. const service = container.get(MyService, 'baz', 'qux');
  142. expect(service).to.be.instanceof(MyService);
  143. expect(executed).to.be.eq(1);
  144. expect(givenContainer).to.be.eq(container);
  145. expect(givenArgs).to.be.eql(['baz', 'qux']);
  146. });
  147. it('caches a new instance', function () {
  148. let executed = 0;
  149. class MyService extends Service {
  150. constructor(container) {
  151. super(container);
  152. ++executed;
  153. }
  154. }
  155. const container = new ServiceContainer();
  156. const service1 = container.get(MyService);
  157. const service2 = container.get(MyService);
  158. expect(service1).to.be.instanceof(MyService);
  159. expect(service2).to.be.instanceof(MyService);
  160. expect(service1).to.be.eq(service2);
  161. expect(executed).to.be.eq(1);
  162. });
  163. it('overrides the cached instance', function () {
  164. let executed = 0;
  165. const givenArgs = [];
  166. class MyService extends Service {
  167. constructor(container, arg) {
  168. super(container);
  169. ++executed;
  170. givenArgs.push(arg);
  171. }
  172. }
  173. const container = new ServiceContainer();
  174. const service1 = container.get(MyService, 'foo');
  175. const service2 = container.get(MyService);
  176. const service3 = container.get(MyService, 'bar');
  177. const service4 = container.get(MyService);
  178. expect(service1).to.be.instanceof(MyService);
  179. expect(service2).to.be.instanceof(MyService);
  180. expect(service3).to.be.instanceof(MyService);
  181. expect(service4).to.be.instanceof(MyService);
  182. expect(service1).to.be.eq(service2);
  183. expect(service2).to.be.not.eq(service3);
  184. expect(service3).to.be.eq(service4);
  185. expect(executed).to.be.eq(2);
  186. expect(givenArgs).to.be.eql(['foo', 'bar']);
  187. });
  188. describe('class inheritance', function () {
  189. it('should create an instance of the child class when the parent class is requested', function () {
  190. class ParentService extends Service {}
  191. class ChildService extends ParentService {}
  192. const container = new ServiceContainer();
  193. container.add(ChildService);
  194. const childService = container.get(ParentService);
  195. expect(childService).to.be.instanceof(ChildService);
  196. });
  197. it('should return the existing instance of the child class when the parent class is requested', function () {
  198. class ParentService extends Service {}
  199. class ChildService extends ParentService {}
  200. const container = new ServiceContainer();
  201. const childService1 = container.get(ChildService);
  202. expect(childService1).to.be.instanceof(ChildService);
  203. const childService2 = container.get(ParentService);
  204. expect(childService1).to.be.instanceof(ChildService);
  205. expect(childService2).to.be.eq(childService1);
  206. });
  207. it('should create an instance of the requested child class, even if its parent class is registered', function () {
  208. class ParentService extends Service {}
  209. class ChildService extends ParentService {}
  210. const container = new ServiceContainer();
  211. container.add(ParentService);
  212. const childService = container.get(ChildService);
  213. expect(childService).to.be.instanceof(ChildService);
  214. });
  215. it('should return an instance of the requested child class, not the existing instance of its parent class', function () {
  216. class ParentService extends Service {}
  217. class ChildService extends ParentService {}
  218. const container = new ServiceContainer();
  219. const parentService = container.get(ParentService);
  220. expect(parentService).to.be.instanceof(ParentService);
  221. const childService = container.get(ChildService);
  222. expect(childService).to.be.instanceof(ChildService);
  223. expect(childService).to.be.not.eq(parentService);
  224. });
  225. });
  226. describe('in case of a parent container', function () {
  227. it('instantiates in the parent container', function () {
  228. class MyService extends Service {}
  229. const parent = new ServiceContainer();
  230. parent.add(MyService);
  231. const child = new ServiceContainer(parent);
  232. const res = child.get(MyService);
  233. expect(res).to.be.instanceof(MyService);
  234. });
  235. it('returns an instance from the parent container', function () {
  236. class MyService extends Service {}
  237. const parent = new ServiceContainer();
  238. const service = parent.get(MyService);
  239. const child = new ServiceContainer(parent);
  240. const res = child.get(MyService);
  241. expect(res).to.be.eq(service);
  242. });
  243. it('does not adds the given constructor to the parent container', function () {
  244. class MyService extends Service {}
  245. const parent = new ServiceContainer();
  246. const child = new ServiceContainer(parent);
  247. const service = child.get(MyService);
  248. expect(service).to.be.instanceof(MyService);
  249. const res1 = child.has(MyService);
  250. const res2 = parent.has(MyService);
  251. expect(res1).to.be.true;
  252. expect(res2).to.be.false;
  253. });
  254. describe('class inheritance', function () {
  255. it('should resolve a parent class to an instance of its child class registered in a parent container', function () {
  256. class ParentService extends Service {}
  257. class ChildService extends ParentService {}
  258. const parentContainer = new ServiceContainer();
  259. const container = new ServiceContainer(parentContainer);
  260. parentContainer.add(ChildService);
  261. const childService = container.get(ParentService);
  262. expect(childService).to.be.instanceof(ChildService);
  263. });
  264. it('should return the existing instance of the child class from a parent container when the parent class is requested', function () {
  265. class ParentService extends Service {}
  266. class ChildService extends ParentService {}
  267. const parentContainer = new ServiceContainer();
  268. const container = new ServiceContainer(parentContainer);
  269. const childService1 = parentContainer.get(ChildService);
  270. expect(childService1).to.be.instanceof(ChildService);
  271. const childService2 = container.get(ParentService);
  272. expect(childService1).to.be.instanceof(ChildService);
  273. expect(childService2).to.be.eq(childService1);
  274. });
  275. it('should create an instance of the requested child class, even if its parent class is registered in a parent container', function () {
  276. class ParentService extends Service {}
  277. class ChildService extends ParentService {}
  278. const parentContainer = new ServiceContainer();
  279. const container = new ServiceContainer(parentContainer);
  280. parentContainer.add(ParentService);
  281. const childService = container.get(ChildService);
  282. expect(childService).to.be.instanceof(ChildService);
  283. });
  284. it('should create an instance of the requested child class, even if a parent container has an instance of its parent class', function () {
  285. class ParentService extends Service {}
  286. class ChildService extends ParentService {}
  287. const parentContainer = new ServiceContainer();
  288. const container = new ServiceContainer(parentContainer);
  289. const parentService = parentContainer.get(ParentService);
  290. expect(parentService).to.be.instanceof(ParentService);
  291. const childService = container.get(ChildService);
  292. expect(childService).to.be.instanceof(ChildService);
  293. expect(childService).to.be.not.eq(parentService);
  294. });
  295. });
  296. });
  297. });
  298. describe('non-Service', function () {
  299. it('passes given arguments to the given constructor', function () {
  300. let executed = 0;
  301. let givenArgs;
  302. class MyService {
  303. constructor(...args) {
  304. executed++;
  305. givenArgs = args;
  306. }
  307. }
  308. const container = new ServiceContainer();
  309. const service = container.get(MyService, 'foo', 'bar');
  310. expect(service).to.be.instanceof(MyService);
  311. expect(executed).to.be.eq(1);
  312. expect(givenArgs).to.be.eql(['foo', 'bar']);
  313. });
  314. it('instantiates from an existing factory function', function () {
  315. let executed = 0;
  316. let givenArgs;
  317. class MyService {
  318. constructor(...args) {
  319. executed++;
  320. givenArgs = args;
  321. }
  322. }
  323. const container = new ServiceContainer();
  324. container.add(MyService, 'foo', 'bar');
  325. expect(executed).to.be.eq(0);
  326. const service = container.get(MyService);
  327. expect(service).to.be.instanceof(MyService);
  328. expect(executed).to.be.eq(1);
  329. expect(givenArgs).to.be.eql(['foo', 'bar']);
  330. });
  331. it('overrides an existing factory function', function () {
  332. let executed = 0;
  333. let givenArgs;
  334. class MyService {
  335. constructor(...args) {
  336. executed++;
  337. givenArgs = args;
  338. }
  339. }
  340. const container = new ServiceContainer();
  341. container.add(MyService, 'foo', 'bar');
  342. expect(executed).to.be.eq(0);
  343. const service = container.get(MyService, 'baz', 'qux');
  344. expect(service).to.be.instanceof(MyService);
  345. expect(executed).to.be.eq(1);
  346. expect(givenArgs).to.be.eql(['baz', 'qux']);
  347. });
  348. it('caches a new instance', function () {
  349. let executed = 0;
  350. class MyService {
  351. constructor() {
  352. ++executed;
  353. }
  354. }
  355. const container = new ServiceContainer();
  356. const service1 = container.get(MyService);
  357. const service2 = container.get(MyService);
  358. expect(service1).to.be.instanceof(MyService);
  359. expect(service2).to.be.instanceof(MyService);
  360. expect(service1).to.be.eq(service2);
  361. expect(executed).to.be.eq(1);
  362. });
  363. it('overrides the cached instance', function () {
  364. let executed = 0;
  365. const givenArgs = [];
  366. class MyService {
  367. constructor(arg) {
  368. ++executed;
  369. givenArgs.push(arg);
  370. }
  371. }
  372. const container = new ServiceContainer();
  373. const service1 = container.get(MyService, 'foo');
  374. const service2 = container.get(MyService);
  375. const service3 = container.get(MyService, 'bar');
  376. const service4 = container.get(MyService);
  377. expect(service1).to.be.instanceof(MyService);
  378. expect(service2).to.be.instanceof(MyService);
  379. expect(service3).to.be.instanceof(MyService);
  380. expect(service4).to.be.instanceof(MyService);
  381. expect(service1).to.be.eq(service2);
  382. expect(service2).to.be.not.eq(service3);
  383. expect(service3).to.be.eq(service4);
  384. expect(executed).to.be.eq(2);
  385. expect(givenArgs).to.be.eql(['foo', 'bar']);
  386. });
  387. describe('class inheritance', function () {
  388. it('should create an instance of the child class when the parent class is requested', function () {
  389. class ParentService {}
  390. class ChildService extends ParentService {}
  391. const container = new ServiceContainer();
  392. container.add(ChildService);
  393. const childService = container.get(ParentService);
  394. expect(childService).to.be.instanceof(ChildService);
  395. });
  396. it('should return the existing instance of the child class when the parent class is requested', function () {
  397. class ParentService {}
  398. class ChildService extends ParentService {}
  399. const container = new ServiceContainer();
  400. const childService1 = container.get(ChildService);
  401. expect(childService1).to.be.instanceof(ChildService);
  402. const childService2 = container.get(ParentService);
  403. expect(childService1).to.be.instanceof(ChildService);
  404. expect(childService2).to.be.eq(childService1);
  405. });
  406. it('should create an instance of the requested child class, even if its parent class is registered', function () {
  407. class ParentService {}
  408. class ChildService extends ParentService {}
  409. const container = new ServiceContainer();
  410. container.add(ParentService);
  411. const childService = container.get(ChildService);
  412. expect(childService).to.be.instanceof(ChildService);
  413. });
  414. it('should return an instance of the requested child class, not the existing instance of its parent class', function () {
  415. class ParentService {}
  416. class ChildService extends ParentService {}
  417. const container = new ServiceContainer();
  418. const parentService = container.get(ParentService);
  419. expect(parentService).to.be.instanceof(ParentService);
  420. const childService = container.get(ChildService);
  421. expect(childService).to.be.instanceof(ChildService);
  422. expect(childService).to.be.not.eq(parentService);
  423. });
  424. });
  425. describe('in case of a parent container', function () {
  426. it('instantiates in the parent container', function () {
  427. class MyService {}
  428. const parent = new ServiceContainer();
  429. parent.add(MyService);
  430. const child = new ServiceContainer(parent);
  431. const res = child.get(MyService);
  432. expect(res).to.be.instanceof(MyService);
  433. });
  434. it('returns an instance from the parent container', function () {
  435. class MyService {}
  436. const parent = new ServiceContainer();
  437. const service = parent.get(MyService);
  438. const child = new ServiceContainer(parent);
  439. const res = child.get(MyService);
  440. expect(res).to.be.eq(service);
  441. });
  442. it('does not adds the given constructor to the parent container', function () {
  443. class MyService {}
  444. const parent = new ServiceContainer();
  445. const child = new ServiceContainer(parent);
  446. const service = child.get(MyService);
  447. expect(service).to.be.instanceof(MyService);
  448. const res1 = child.has(MyService);
  449. const res2 = parent.has(MyService);
  450. expect(res1).to.be.true;
  451. expect(res2).to.be.false;
  452. });
  453. describe('class inheritance', function () {
  454. it('should resolve a parent class to an instance of its child class registered in a parent container', function () {
  455. class ParentService {}
  456. class ChildService extends ParentService {}
  457. const parentContainer = new ServiceContainer();
  458. const container = new ServiceContainer(parentContainer);
  459. parentContainer.add(ChildService);
  460. const childService = container.get(ParentService);
  461. expect(childService).to.be.instanceof(ChildService);
  462. });
  463. it('should return the existing instance of the child class from a parent container when the parent class is requested', function () {
  464. class ParentService {}
  465. class ChildService extends ParentService {}
  466. const parentContainer = new ServiceContainer();
  467. const container = new ServiceContainer(parentContainer);
  468. const childService1 = parentContainer.get(ChildService);
  469. expect(childService1).to.be.instanceof(ChildService);
  470. const childService2 = container.get(ParentService);
  471. expect(childService1).to.be.instanceof(ChildService);
  472. expect(childService2).to.be.eq(childService1);
  473. });
  474. it('should create an instance of the requested child class, even if its parent class is registered in a parent container', function () {
  475. class ParentService {}
  476. class ChildService extends ParentService {}
  477. const parentContainer = new ServiceContainer();
  478. const container = new ServiceContainer(parentContainer);
  479. parentContainer.add(ParentService);
  480. const childService = container.get(ChildService);
  481. expect(childService).to.be.instanceof(ChildService);
  482. });
  483. it('should create an instance of the requested child class, even if a parent container has an instance of its parent class', function () {
  484. class ParentService {}
  485. class ChildService extends ParentService {}
  486. const parentContainer = new ServiceContainer();
  487. const container = new ServiceContainer(parentContainer);
  488. const parentService = parentContainer.get(ParentService);
  489. expect(parentService).to.be.instanceof(ParentService);
  490. const childService = container.get(ChildService);
  491. expect(childService).to.be.instanceof(ChildService);
  492. expect(childService).to.be.not.eq(parentService);
  493. });
  494. });
  495. });
  496. });
  497. });
  498. describe('has', function () {
  499. describe('Service', function () {
  500. it('returns true when a given constructor has its cached instance or false', function () {
  501. const container = new ServiceContainer();
  502. class MyService extends Service {}
  503. expect(container.has(MyService)).to.be.false;
  504. container.get(MyService);
  505. expect(container.has(MyService)).to.be.true;
  506. });
  507. it('returns true when a given constructor has its factory function or false', function () {
  508. const container = new ServiceContainer();
  509. class MyService extends Service {}
  510. expect(container.has(MyService)).to.be.false;
  511. container.add(MyService);
  512. expect(container.has(MyService)).to.be.true;
  513. });
  514. describe('class inheritance', function () {
  515. it('returns true if the child class is registered', function () {
  516. class ParentService extends Service {}
  517. class ChildService extends ParentService {}
  518. const container = new ServiceContainer();
  519. container.add(ChildService);
  520. const res = container.has(ParentService);
  521. expect(res).to.be.true;
  522. });
  523. it('returns false if the parent class is registered', function () {
  524. class ParentService extends Service {}
  525. class ChildService extends ParentService {}
  526. const container = new ServiceContainer();
  527. container.add(ParentService);
  528. const res = container.has(ChildService);
  529. expect(res).to.be.false;
  530. });
  531. });
  532. describe('in case of a parent container', function () {
  533. it('returns true if the parent container has the given constructor', function () {
  534. class MyService extends Service {}
  535. const parent = new ServiceContainer();
  536. parent.add(MyService);
  537. const child = new ServiceContainer(parent);
  538. const res = child.has(MyService);
  539. expect(res).to.be.true;
  540. });
  541. });
  542. });
  543. describe('non-Service', function () {
  544. it('returns true when a given constructor has its cached instance or false', function () {
  545. const container = new ServiceContainer();
  546. class MyService {}
  547. expect(container.has(MyService)).to.be.false;
  548. container.get(MyService);
  549. expect(container.has(MyService)).to.be.true;
  550. });
  551. it('returns true when a given constructor has its factory function or false', function () {
  552. const container = new ServiceContainer();
  553. class MyService {}
  554. expect(container.has(MyService)).to.be.false;
  555. container.add(MyService);
  556. expect(container.has(MyService)).to.be.true;
  557. });
  558. describe('class inheritance', function () {
  559. it('returns true if the child class is registered', function () {
  560. class ParentService {}
  561. class ChildService extends ParentService {}
  562. const container = new ServiceContainer();
  563. container.add(ChildService);
  564. const res = container.has(ParentService);
  565. expect(res).to.be.true;
  566. });
  567. it('returns false if the parent class is registered', function () {
  568. class ParentService {}
  569. class ChildService extends ParentService {}
  570. const container = new ServiceContainer();
  571. container.add(ParentService);
  572. const res = container.has(ChildService);
  573. expect(res).to.be.false;
  574. });
  575. });
  576. describe('in case of a parent container', function () {
  577. it('returns true if the parent container has the given constructor', function () {
  578. class MyService {}
  579. const parent = new ServiceContainer();
  580. parent.add(MyService);
  581. const child = new ServiceContainer(parent);
  582. const res = child.has(MyService);
  583. expect(res).to.be.true;
  584. });
  585. });
  586. });
  587. });
  588. describe('add', function () {
  589. it('throws an error if no constructor given', function () {
  590. const container = new ServiceContainer();
  591. const throwable = v => () => container.add(v);
  592. const error = v =>
  593. format(
  594. 'The first argument of ServicesContainer.add must be ' +
  595. 'a class constructor, but %s given.',
  596. v,
  597. );
  598. expect(throwable()).to.throw(error('undefined'));
  599. expect(throwable('str')).to.throw(error('"str"'));
  600. expect(throwable(10)).to.throw(error('10'));
  601. expect(throwable(true)).to.throw(error('true'));
  602. expect(throwable(false)).to.throw(error('false'));
  603. expect(throwable(null)).to.throw(error('null'));
  604. expect(throwable([])).to.throw(error('Array'));
  605. expect(throwable({})).to.throw(error('Object'));
  606. });
  607. describe('Service', function () {
  608. it('returns itself', function () {
  609. class MyService extends Service {}
  610. const container = new ServiceContainer();
  611. const res = container.add(MyService);
  612. expect(res).to.be.eq(container);
  613. });
  614. it('provides given arguments to the factory function', function () {
  615. let executed = 0;
  616. let givenContainer;
  617. let givenArgs;
  618. class MyService extends Service {
  619. constructor(container, ...args) {
  620. super(container);
  621. executed++;
  622. givenContainer = container;
  623. givenArgs = args;
  624. }
  625. }
  626. const container = new ServiceContainer();
  627. container.add(MyService, 'foo', 'bar');
  628. expect(executed).to.be.eq(0);
  629. const service = container.get(MyService);
  630. expect(service).to.be.instanceof(MyService);
  631. expect(executed).to.be.eq(1);
  632. expect(givenContainer).to.be.eq(container);
  633. expect(givenArgs).to.be.eql(['foo', 'bar']);
  634. });
  635. it('overrides a cached instance of the given constructor', function () {
  636. class MyService extends Service {}
  637. const container = new ServiceContainer();
  638. const service1 = container.get(MyService);
  639. const service2 = container.get(MyService);
  640. expect(service1).to.be.eq(service2);
  641. container.add(MyService);
  642. const service3 = container.get(MyService);
  643. const service4 = container.get(MyService);
  644. expect(service3).to.be.eq(service4);
  645. expect(service3).to.be.not.eq(service1);
  646. });
  647. it('overrides constructor arguments of the factory function', function () {
  648. let executed = 0;
  649. let givenContainer;
  650. let givenArgs;
  651. class MyService extends Service {
  652. constructor(container, ...args) {
  653. super(container);
  654. executed++;
  655. givenContainer = container;
  656. givenArgs = args;
  657. }
  658. }
  659. const container = new ServiceContainer();
  660. container.add(MyService, 'foo', 'bar');
  661. expect(executed).to.be.eq(0);
  662. container.add(MyService, 'baz', 'qux');
  663. const service = container.get(MyService);
  664. expect(service).to.be.instanceof(MyService);
  665. expect(executed).to.be.eq(1);
  666. expect(givenContainer).to.be.eq(container);
  667. expect(givenArgs).to.be.eql(['baz', 'qux']);
  668. });
  669. });
  670. describe('non-Service', function () {
  671. it('returns itself', function () {
  672. class MyService {}
  673. const container = new ServiceContainer();
  674. const res = container.add(MyService);
  675. expect(res).to.be.eq(container);
  676. });
  677. it('provides given arguments to the factory function', function () {
  678. let executed = 0;
  679. let givenArgs;
  680. class MyService {
  681. constructor(...args) {
  682. executed++;
  683. givenArgs = args;
  684. }
  685. }
  686. const container = new ServiceContainer();
  687. container.add(MyService, 'foo', 'bar');
  688. expect(executed).to.be.eq(0);
  689. const service = container.get(MyService);
  690. expect(service).to.be.instanceof(MyService);
  691. expect(executed).to.be.eq(1);
  692. expect(givenArgs).to.be.eql(['foo', 'bar']);
  693. });
  694. it('overrides a cached instance of the given constructor', function () {
  695. class MyService {}
  696. const container = new ServiceContainer();
  697. const service1 = container.get(MyService);
  698. const service2 = container.get(MyService);
  699. expect(service1).to.be.eq(service2);
  700. container.add(MyService);
  701. const service3 = container.get(MyService);
  702. const service4 = container.get(MyService);
  703. expect(service3).to.be.eq(service4);
  704. expect(service3).to.be.not.eq(service1);
  705. });
  706. it('overrides constructor arguments of the factory function', function () {
  707. let executed = 0;
  708. let givenArgs;
  709. class MyService {
  710. constructor(...args) {
  711. executed++;
  712. givenArgs = args;
  713. }
  714. }
  715. const container = new ServiceContainer();
  716. container.add(MyService, 'foo', 'bar');
  717. expect(executed).to.be.eq(0);
  718. container.add(MyService, 'baz', 'qux');
  719. const service = container.get(MyService);
  720. expect(service).to.be.instanceof(MyService);
  721. expect(executed).to.be.eq(1);
  722. expect(givenArgs).to.be.eql(['baz', 'qux']);
  723. });
  724. });
  725. });
  726. describe('use', function () {
  727. it('throws an error if no constructor given', function () {
  728. const container = new ServiceContainer();
  729. const throwable = v => () => container.use(v);
  730. const error = v =>
  731. format(
  732. 'The first argument of ServicesContainer.use must be ' +
  733. 'a class constructor, but %s given.',
  734. v,
  735. );
  736. expect(throwable()).to.throw(error('undefined'));
  737. expect(throwable('str')).to.throw(error('"str"'));
  738. expect(throwable(10)).to.throw(error('10'));
  739. expect(throwable(true)).to.throw(error('true'));
  740. expect(throwable(false)).to.throw(error('false'));
  741. expect(throwable(null)).to.throw(error('null'));
  742. expect(throwable([])).to.throw(error('Array'));
  743. expect(throwable({})).to.throw(error('Object'));
  744. });
  745. describe('Service', function () {
  746. it('returns itself', function () {
  747. class MyService extends Service {}
  748. const container = new ServiceContainer();
  749. const res = container.use(MyService);
  750. expect(res).to.be.eq(container);
  751. });
  752. it('passes itself and given arguments to the given constructor', function () {
  753. let executed = 0;
  754. let givenContainer;
  755. let givenArgs;
  756. class MyService extends Service {
  757. constructor(container, ...args) {
  758. super(container);
  759. executed++;
  760. givenContainer = container;
  761. givenArgs = args;
  762. }
  763. }
  764. const container = new ServiceContainer();
  765. container.use(MyService, 'foo', 'bar');
  766. expect(executed).to.be.eq(1);
  767. expect(givenContainer).to.be.eq(container);
  768. expect(givenArgs).to.be.eql(['foo', 'bar']);
  769. });
  770. it('overrides an existing factory function', function () {
  771. let executed = 0;
  772. let givenContainer;
  773. let givenArgs;
  774. class MyService extends Service {
  775. constructor(container, ...args) {
  776. super(container);
  777. executed++;
  778. givenContainer = container;
  779. givenArgs = args;
  780. }
  781. }
  782. const container = new ServiceContainer();
  783. container.add(MyService, 'foo', 'bar');
  784. expect(executed).to.be.eq(0);
  785. container.use(MyService, 'baz', 'qux');
  786. expect(executed).to.be.eq(1);
  787. expect(givenContainer).to.be.eq(container);
  788. expect(givenArgs).to.be.eql(['baz', 'qux']);
  789. });
  790. it('caches a new instance', function () {
  791. let executed = 0;
  792. let service;
  793. class MyService extends Service {
  794. constructor(container) {
  795. super(container);
  796. ++executed;
  797. service = this;
  798. }
  799. }
  800. const container = new ServiceContainer();
  801. container.use(MyService);
  802. const cachedService = container.get(MyService);
  803. expect(cachedService).to.be.instanceof(MyService);
  804. expect(cachedService).to.be.eq(service);
  805. expect(executed).to.be.eq(1);
  806. });
  807. it('overrides the cached instance', function () {
  808. let executed = 0;
  809. let service;
  810. let givenArgs;
  811. class MyService extends Service {
  812. constructor(container, ...args) {
  813. super(container);
  814. ++executed;
  815. service = this;
  816. givenArgs = args;
  817. }
  818. }
  819. const container = new ServiceContainer();
  820. container.use(MyService, 'foo');
  821. expect(executed).to.be.eq(1);
  822. expect(service).to.be.instanceof(MyService);
  823. expect(givenArgs).to.be.eql(['foo']);
  824. const service1 = service;
  825. container.use(MyService, 'bar');
  826. expect(executed).to.be.eq(2);
  827. expect(service).to.be.instanceof(MyService);
  828. expect(givenArgs).to.be.eql(['bar']);
  829. const service2 = service;
  830. expect(service2).to.be.not.eq(service1);
  831. });
  832. });
  833. describe('non-Service', function () {
  834. it('returns itself', function () {
  835. class MyService {}
  836. const container = new ServiceContainer();
  837. const res = container.use(MyService);
  838. expect(res).to.be.eq(container);
  839. });
  840. it('passes given arguments to the given constructor', function () {
  841. let executed = 0;
  842. let givenArgs;
  843. class MyService {
  844. constructor(...args) {
  845. executed++;
  846. givenArgs = args;
  847. }
  848. }
  849. const container = new ServiceContainer();
  850. container.use(MyService, 'foo', 'bar');
  851. expect(executed).to.be.eq(1);
  852. expect(givenArgs).to.be.eql(['foo', 'bar']);
  853. });
  854. it('overrides an existing factory function', function () {
  855. let executed = 0;
  856. let givenArgs;
  857. class MyService {
  858. constructor(...args) {
  859. executed++;
  860. givenArgs = args;
  861. }
  862. }
  863. const container = new ServiceContainer();
  864. container.add(MyService, 'foo', 'bar');
  865. expect(executed).to.be.eq(0);
  866. container.use(MyService, 'baz', 'qux');
  867. expect(executed).to.be.eq(1);
  868. expect(givenArgs).to.be.eql(['baz', 'qux']);
  869. });
  870. it('caches a new instance', function () {
  871. let executed = 0;
  872. let service;
  873. class MyService {
  874. constructor() {
  875. ++executed;
  876. service = this;
  877. }
  878. }
  879. const container = new ServiceContainer();
  880. container.use(MyService);
  881. const cachedService = container.get(MyService);
  882. expect(cachedService).to.be.instanceof(MyService);
  883. expect(cachedService).to.be.eq(service);
  884. expect(executed).to.be.eq(1);
  885. });
  886. it('overrides the cached instance', function () {
  887. let executed = 0;
  888. let service;
  889. let givenArgs;
  890. class MyService {
  891. constructor(...args) {
  892. ++executed;
  893. service = this;
  894. givenArgs = args;
  895. }
  896. }
  897. const container = new ServiceContainer();
  898. container.use(MyService, 'foo');
  899. expect(executed).to.be.eq(1);
  900. expect(service).to.be.instanceof(MyService);
  901. expect(givenArgs).to.be.eql(['foo']);
  902. const service1 = service;
  903. container.use(MyService, 'bar');
  904. expect(executed).to.be.eq(2);
  905. expect(service).to.be.instanceof(MyService);
  906. expect(givenArgs).to.be.eql(['bar']);
  907. const service2 = service;
  908. expect(service2).to.be.not.eq(service1);
  909. });
  910. });
  911. });
  912. describe('set', function () {
  913. it('requires the "ctor" argument to be a class constructor', function () {
  914. const container = new ServiceContainer();
  915. const throwable = v => () => container.set(v, {});
  916. const error = v =>
  917. format(
  918. 'The first argument of ServicesContainer.set must be ' +
  919. 'a class constructor, but %s given.',
  920. v,
  921. );
  922. expect(throwable()).to.throw(error('undefined'));
  923. expect(throwable('str')).to.throw(error('"str"'));
  924. expect(throwable(10)).to.throw(error('10'));
  925. expect(throwable(true)).to.throw(error('true'));
  926. expect(throwable(false)).to.throw(error('false'));
  927. expect(throwable(null)).to.throw(error('null'));
  928. expect(throwable([])).to.throw(error('Array'));
  929. expect(throwable({})).to.throw(error('Object'));
  930. throwable(String)();
  931. });
  932. it('requires the "service" argument to be an Object', function () {
  933. const container = new ServiceContainer();
  934. const throwable = v => () => container.set(String, v);
  935. const error = v =>
  936. format(
  937. 'The second argument of ServicesContainer.set must be ' +
  938. 'an Object, but %s given.',
  939. v,
  940. );
  941. expect(throwable()).to.throw(error('undefined'));
  942. expect(throwable('str')).to.throw(error('"str"'));
  943. expect(throwable(10)).to.throw(error('10'));
  944. expect(throwable(true)).to.throw(error('true'));
  945. expect(throwable(false)).to.throw(error('false'));
  946. expect(throwable(null)).to.throw(error('null'));
  947. expect(throwable([])).to.throw(error('Array'));
  948. throwable({})();
  949. });
  950. describe('Service', function () {
  951. it('returns itself', function () {
  952. class MyService extends Service {}
  953. const container = new ServiceContainer();
  954. const res = container.set(MyService, {});
  955. expect(res).to.be.eq(container);
  956. });
  957. it('sets the given service', function () {
  958. class MyService extends Service {}
  959. const container = new ServiceContainer();
  960. const service = {};
  961. container.set(MyService, service);
  962. const res = container.get(MyService);
  963. expect(res).to.be.eq(service);
  964. });
  965. it('overrides by the given service', function () {
  966. class MyService extends Service {}
  967. const container = new ServiceContainer();
  968. const service1 = {foo: 'bar'};
  969. const service2 = {bar: 'baz'};
  970. container.set(MyService, service1);
  971. container.set(MyService, service2);
  972. const res = container.get(MyService);
  973. expect(res).to.be.eq(service2);
  974. });
  975. });
  976. describe('non-Service', function () {
  977. it('returns itself', function () {
  978. class MyService {}
  979. const container = new ServiceContainer();
  980. const res = container.set(MyService, {});
  981. expect(res).to.be.eq(container);
  982. });
  983. it('sets the given service', function () {
  984. class MyService {}
  985. const container = new ServiceContainer();
  986. const service = {};
  987. container.set(MyService, service);
  988. const res = container.get(MyService);
  989. expect(res).to.be.eq(service);
  990. });
  991. it('overrides by the given service', function () {
  992. class MyService {}
  993. const container = new ServiceContainer();
  994. const service1 = {foo: 'bar'};
  995. const service2 = {bar: 'baz'};
  996. container.set(MyService, service1);
  997. container.set(MyService, service2);
  998. const res = container.get(MyService);
  999. expect(res).to.be.eq(service2);
  1000. });
  1001. });
  1002. });
  1003. });