service-container.spec.js 40 KB

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