locustjs-locator
Version:
A locustjs library implementing Service Locator pattern to provide loose coupling through IoC (Inversion of Control).
462 lines (326 loc) • 19.4 kB
JavaScript
import { DefaultLocator, DefaultStorage, Resolve } from '../index.esm.js';
import {
FooServiceAbstract,
FooServiceConcreteIndependent,
FooServiceConcrete,
BarServiceAbstract,
BarServiceConcrete,
BazServiceAbstract1,
BazServiceConcrete1,
BazServiceAbstract2,
BazServiceConcrete2,
BazServiceAbstract3,
BazServiceConcrete3,
BazServiceAbstract4,
BazServiceConcrete4,
BazServiceAbstract5,
BazServiceConcrete5,
BazServiceAbstract6,
BazServiceConcrete6
} from './SampleServices';
// this IIF is able to test a locator thoroughly.
// if a new locator is developed, in order to test it
// we just need to add a factory config argument.
(function (...locatorFactoryConfigs) {
for (let locatorFactoryConfig of locatorFactoryConfigs) {
describe('Testing ' + locatorFactoryConfig.name, () => {
// --------------------- register -------------------
test('register(): register a base class by a non-child class', () => {
expect(() => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcreteIndependent);
}).toThrow()
});
test('register(): register a base class by a child class', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
const exists = locator.exists(FooServiceAbstract);
expect(exists).toBe(true);
});
test('remove(): removing a none-existing entry', () => {
const locator = locatorFactoryConfig.factory();
const result = locator.remove(FooServiceAbstract);
expect(result).toBe(false);
});
// --------------------- remove -------------------
test('remove(): removing an existing entry by its abstraction', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
const result = locator.remove(FooServiceAbstract);
const exists = locator.exists(FooServiceAbstract);
expect(result).toBe(true);
expect(exists).toBe(false);
});
test('remove(): removing an existing entry by its index', () => {
const locator = locatorFactoryConfig.factory();
const index = locator.register(FooServiceAbstract, FooServiceConcrete);
const result = locator.remove(index);
const exists = locator.exists(FooServiceAbstract);
expect(result).toBe(true);
expect(exists).toBe(false);
});
// --------------------- indexOf -------------------
test('indexOf(): returns index of a registration entry', () => {
const locator = locatorFactoryConfig.factory();
const r1 = locator.register(FooServiceAbstract, FooServiceConcrete);
const r2 = locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 1);
const index1 = locator.indexOf(FooServiceAbstract);
const index2 = locator.indexOf(FooServiceAbstract, 1);
expect(r1).toBe(0);
expect(r2).toBe(1);
expect(index1).toBe(0);
expect(index2).toBe(1);
});
// --------------------- args for constructor -------------------
test('resolve(A, ...args): args should be passed to dependency', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
const x = locator.resolve(FooServiceAbstract, 'F1', 10);
expect(x).toBeDefined();
expect(x.code).toBe('F1');
expect(x.size).toBe(10);
});
// --------------------- state -------------------
test('register(A, B, any, state): should resolve based on state', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 1);
const x = locator.resolve(FooServiceAbstract);
expect(x).toBeFalsy();
});
test('register(A, B, any, state): should resolve based on state', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 1);
const x = locator.resolveBy(FooServiceAbstract, 1);
expect(x).toBeTruthy();
});
test('register(A, B, any, state): should be able to register same abstraction with various states', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 1);
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 2);
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 3);
const x1 = locator.resolveBy(FooServiceAbstract, 1);
const x2 = locator.resolveBy(FooServiceAbstract, 2);
const x3 = locator.resolveBy(FooServiceAbstract, 3);
expect(locator.length).toBe(3);
expect(x1).toBeTruthy();
expect(x2).toBeTruthy();
expect(x3).toBeTruthy();
});
// --------------------- state and args for constructor -------------------
test('register(A, B, any, state, args): should resolve based on state and args should be passed to its constructor', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 1);
const x = locator.resolveBy(FooServiceAbstract, 1, 'F1', 10);
expect(x).toBeDefined();
expect(x.code).toBe('F1');
expect(x.size).toBe(10);
});
// --------------------- register/resolve -------------------
test('register(A, B): resolve() should return different objects', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
const f1 = locator.resolve(FooServiceAbstract);
const f2 = locator.resolve(FooServiceAbstract);
expect(f1 == f2).toBe(false);
});
// --------------------- registerInstance -------------------
test('registerInstance(A, B): resolve() should return same objects <INSTANCE>', () => {
const locator = locatorFactoryConfig.factory();
locator.registerInstance(FooServiceAbstract, new FooServiceConcrete());
const f1 = locator.resolve(FooServiceAbstract);
const f2 = locator.resolve(FooServiceAbstract);
expect(f1 == f2).toBe(true);
});
test('registerInstance(A, B): resolve() should return same objects <FUNCTION>', () => {
const locator = locatorFactoryConfig.factory();
locator.registerInstance(FooServiceAbstract, () => new FooServiceConcrete());
const f1 = locator.resolve(FooServiceAbstract);
const f2 = locator.resolve(FooServiceAbstract);
expect(f1 == f2).toBe(true);
});
// --------------------- registerFactory -------------------
test('registerFactory(A, B)', () => {
const locator = locatorFactoryConfig.factory();
locator.registerFactory(FooServiceAbstract, () => new FooServiceConcrete());
const f = locator.resolve(FooServiceAbstract);
expect(f).toBeDefined();
});
test('registerFactory(A, B)', () => {
const locator = locatorFactoryConfig.factory();
locator.registerFactory(FooServiceAbstract, () => new FooServiceConcrete(), Resolve.PerRequest, 1);
const f = locator.resolve(FooServiceAbstract);
expect(f).toBeFalsy();
});
test('registerFactory(A, B)', () => {
const locator = locatorFactoryConfig.factory();
locator.registerFactory(FooServiceAbstract, () => new FooServiceConcrete(), Resolve.PerRequest, 1);
const f = locator.resolveBy(FooServiceAbstract, 1);
expect(f).toBeDefined();
});
// --------------------- register(A, B, Resolve.PerApp): singleton (localStorage) -------------------
test('register(A, B, Resolve.PerApp): should store one object in storage', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerApp);
const f1 = locator.resolve(FooServiceAbstract, 10, 20);
const f2 = locator.resolve(FooServiceAbstract);
expect(locator.getLocalStorage().length == 1).toBe(true);
expect(f1).toBeDefined();
expect(f2).toBeDefined();
expect(f1.code).toBe(f2.code);
expect(f1.size).toBe(f2.size);
});
test('register(A, B, Resolve.PerApp): should store one object in storage', () => {
const locator1 = locatorFactoryConfig.factory();
locator1.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerApp);
const locator2 = locatorFactoryConfig.factory();
locator2.setLocalStorage(locator1.getLocalStorage());
locator2.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerApp);
const f1 = locator1.resolve(FooServiceAbstract, 10, 20);
const f2 = locator2.resolve(FooServiceAbstract);
expect(locator2.getLocalStorage().length == 1).toBe(true);
expect(f1).toBeDefined();
expect(f2).toBeDefined();
expect(f1.code).toBe(f2.code);
expect(f1.size).toBe(f2.size);
});
// --------------------- register(A, B, Resolve.PerSession): singleton (sessionStorage) -------------------
test('register(A, B, Resolve.PerSession): should store one object in storage', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerSession);
const f1 = locator.resolve(FooServiceAbstract, 10, 20);
const f2 = locator.resolve(FooServiceAbstract);
expect(locator.getSessionStorage().length == 1).toBe(true);
expect(f1).toBeDefined();
expect(f2).toBeDefined();
expect(f1.code).toBe(f2.code);
expect(f1.size).toBe(f2.size);
});
test('register(A, B, Resolve.PerSession): should store one object in storage', () => {
const locator1 = locatorFactoryConfig.factory();
locator1.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerSession);
const locator2 = locatorFactoryConfig.factory();
locator2.setSessionStorage(locator1.getSessionStorage());
locator2.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerSession);
const f1 = locator1.resolve(FooServiceAbstract, 10, 20);
const f2 = locator2.resolve(FooServiceAbstract);
expect(locator2.getSessionStorage().length == 1).toBe(true);
expect(f1).toBeDefined();
expect(f2).toBeDefined();
expect(f1.code).toBe(f2.code);
expect(f1.size).toBe(f2.size);
});
// --------------------- register(A, B, Resolve.PerPage): singleton (per page) -------------------
test('register(A, B, Resolve.PerApp): resolve() should store one object in storage', () => {
const storage = new DefaultStorage();
const f1 = (function () {
const locator = locatorFactoryConfig.factory();
locator.setLocalStorage(storage);
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerApp);
const f = locator.resolve(FooServiceAbstract, 10, 20);
return f;
})();
const f2 = (function () {
const locator = locatorFactoryConfig.factory();
locator.setLocalStorage(storage);
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerApp);
const f = locator.resolve(FooServiceAbstract);
return f;
})();
expect(f1).toBeDefined();
expect(f2).toBeDefined();
expect(f1.code).toBe(f2.code);
expect(f1.size).toBe(f2.size);
});
// --------------------- resolve(A, B): dependencies should be resolved automatically -------------------
test('register(A, B): dependencies should be resolved automatically <ex.1>', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
locator.register(BarServiceAbstract, BarServiceConcrete);
locator.register(BazServiceAbstract1, BazServiceConcrete1);
const x = locator.resolve(BazServiceAbstract1);
expect(x).toBeDefined();
expect(x.fooService).toBeDefined();
expect(x.barService).toBeDefined();
});
test('register(A, B): dependencies should be resolved automatically <ex.2>', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
locator.register(BarServiceAbstract, BarServiceConcrete, Resolve.PerRequest, 1);
locator.register(BazServiceAbstract2, BazServiceConcrete2);
const x = locator.resolve(BazServiceAbstract2);
expect(x).toBeDefined();
expect(x.fooService).toBeDefined();
expect(x.barService).toBeDefined();
});
test('register(A, B): dependencies should be resolved automatically <ex.3>', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete);
locator.register(BarServiceAbstract, BarServiceConcrete);
locator.register(BazServiceAbstract3, BazServiceConcrete3);
const x = locator.resolve(BazServiceAbstract3);
expect(x).toBeDefined();
expect(x.fooService).toBeDefined();
expect(x.fooService.code).toBe(10);
expect(x.fooService.size).toBe(20);
expect(x.barService).toBeDefined();
expect(x.barService.name).toBe('B1');
});
test('register(A, B): dependencies should be resolved automatically <ex.4>', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 1);
locator.register(BarServiceAbstract, BarServiceConcrete, Resolve.PerRequest, 2);
locator.register(BazServiceAbstract4, BazServiceConcrete4);
const x = locator.resolve(BazServiceAbstract4);
expect(x).toBeDefined();
expect(x.fooService).toBeDefined();
expect(x.fooService.code).toBe(10);
expect(x.fooService.size).toBe(20);
expect(x.barService).toBeDefined();
expect(x.barService.name).toBe('B1');
});
test('register(A, B): dependencies should be resolved automatically <ex.5>', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 2);
locator.register(BarServiceAbstract, BarServiceConcrete, Resolve.PerRequest);
locator.register(BazServiceAbstract5, BazServiceConcrete5);
const x = locator.resolve(BazServiceAbstract5, {
args: ['baz-title'],
[FooServiceAbstract]: [20, 30],
[BarServiceAbstract]: ['bar-name']
});
expect(x).toBeDefined();
expect(x.title).toBe('baz-title');
expect(x.fooService).toBeDefined();
expect(x.fooService.code).toBe(10);
expect(x.fooService.size).toBe(20);
expect(x.fooService.num).toBe(30);
expect(x.barService).toBeDefined();
expect(x.barService.name).toBe('bar-name');
});
test('register(A, B): dependencies should be resolved automatically <ex.6>', () => {
const locator = locatorFactoryConfig.factory();
locator.register(FooServiceAbstract, FooServiceConcrete, Resolve.PerRequest, 2);
locator.register(BarServiceAbstract, BarServiceConcrete, Resolve.PerRequest);
locator.register(BazServiceAbstract6, BazServiceConcrete6);
const x = locator.resolve(BazServiceAbstract6, {
args: ['baz-title'],
[FooServiceAbstract]: { state: 2, args: [10, 20, 30] },
[BarServiceAbstract]: ['bar-name']
});
expect(x).toBeDefined();
expect(x.title).toBe('baz-title');
expect(x.fooService).toBeDefined();
expect(x.fooService.code).toBe(10);
expect(x.fooService.size).toBe(20);
expect(x.fooService.num).toBe(30);
expect(x.barService).toBeDefined();
expect(x.barService.name).toBe('bar-name');
});
});
}
})({ // factory config item to test DefaultLocator class
name: 'DefaultLocator',
factory: function () {
return new DefaultLocator();
}
});