Skip to content

Commit

Permalink
chore(context): reorganize and clean up tests
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Mar 4, 2019
1 parent 02ea8ac commit 9200f41
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 253 deletions.
2 changes: 1 addition & 1 deletion packages/context/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"build": "lb-tsc es2017 --outDir dist",
"clean": "lb-clean loopback-context*.tgz dist package api-docs",
"pretest": "npm run build",
"test": "lb-mocha \"dist/__tests__/unit/**/*.js\" \"dist/__tests__/acceptance/**/*.js\"",
"test": "lb-mocha \"dist/__tests__/**/*.js\"",
"unit": "lb-mocha \"dist/__tests__/unit/**/*.js\"",
"verify": "npm pack && tar xf loopback-context*.tgz && tree package && npm run clean"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {expect} from '@loopback/testlab';
import {
Binding,
Context,
ContextEventType,
ContextObserver,
filterByTag,
} from '../..';

let app: Context;
let server: Context;

describe('ContextEventObserver', () => {
let contextObserver: MyObserverForControllers;
beforeEach(givenControllerObserver);

it('receives notifications of matching binding events', async () => {
const controllers = await getControllers();
// We have server: ServerController, app: AppController
// NOTE: The controllers are not guaranteed to be ['ServerController`,
// 'AppController'] as the events are emitted by two context objects and
// they are processed asynchronously
expect(controllers).to.containEql('ServerController');
expect(controllers).to.containEql('AppController');
server.unbind('controllers.ServerController');
// Now we have app: AppController
expect(await getControllers()).to.eql(['AppController']);
app.unbind('controllers.AppController');
// All controllers are gone from the context chain
expect(await getControllers()).to.eql([]);
// Add a new controller - server: AnotherServerController
givenController(server, 'AnotherServerController');
expect(await getControllers()).to.eql(['AnotherServerController']);
});

class MyObserverForControllers implements ContextObserver {
controllers: Set<string> = new Set();
filter = filterByTag('controller');
observe(event: ContextEventType, binding: Readonly<Binding<unknown>>) {
if (event === 'bind') {
this.controllers.add(binding.tagMap.name);
} else if (event === 'unbind') {
this.controllers.delete(binding.tagMap.name);
}
}
}

function givenControllerObserver() {
givenServerWithinAnApp();
contextObserver = new MyObserverForControllers();
server.subscribe(contextObserver);
givenController(server, 'ServerController');
givenController(app, 'AppController');
}

function givenController(ctx: Context, controllerName: string) {
class MyController {
name = controllerName;
}
ctx
.bind(`controllers.${controllerName}`)
.toClass(MyController)
.tag('controller', {name: controllerName});
}

async function getControllers() {
return new Promise<string[]>(resolve => {
// Wrap it inside `setImmediate` to make the events are triggered
setImmediate(() => resolve(Array.from(contextObserver.controllers)));
});
}
});

function givenServerWithinAnApp() {
app = new Context('app');
server = new Context(app, 'server');
}
155 changes: 4 additions & 151 deletions packages/context/src/__tests__/acceptance/context-view.acceptance.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import {expect} from '@loopback/testlab';
import {
Binding,
filterByTag,
Context,
ContextObserver,
ContextEventType,
ContextView,
Getter,
inject,
} from '../..';
import {Context, ContextView, filterByTag} from '../..';

let app: Context;
let server: Context;

describe('ContextView', () => {
let contextView: ContextView;
let viewOfControllers: ContextView;
beforeEach(givenViewForControllers);

it('watches matching bindings', async () => {
Expand All @@ -36,7 +27,7 @@ describe('ContextView', () => {

function givenViewForControllers() {
givenServerWithinAnApp();
contextView = server.createView(filterByTag('controller'));
viewOfControllers = server.createView(filterByTag('controller'));
givenController(server, 'ServerController');
givenController(app, 'AppController');
}
Expand All @@ -53,145 +44,7 @@ describe('ContextView', () => {

async function getControllers() {
// tslint:disable-next-line:no-any
return (await contextView.values()).map((v: any) => v.name);
}
});

describe('@inject.* with binding filter', async () => {
const workloadMonitorFilter = filterByTag('workloadMonitor');
beforeEach(givenWorkloadMonitors);

class MyControllerWithGetter {
@inject.getter(workloadMonitorFilter)
getter: Getter<number[]>;
}

class MyControllerWithValues {
constructor(
@inject(workloadMonitorFilter)
public values: number[],
) {}
}

class MyControllerWithView {
@inject.view(workloadMonitorFilter)
view: ContextView<number[]>;
}

it('injects as getter', async () => {
server.bind('my-controller').toClass(MyControllerWithGetter);
const inst = await server.get<MyControllerWithGetter>('my-controller');
const getter = inst.getter;
expect(await getter()).to.eql([3, 5]);
// Add a new binding that matches the filter
givenWorkloadMonitor(server, 'server-reporter-2', 7);
// The getter picks up the new binding
expect(await getter()).to.eql([3, 7, 5]);
});

it('injects as values', async () => {
server.bind('my-controller').toClass(MyControllerWithValues);
const inst = await server.get<MyControllerWithValues>('my-controller');
expect(inst.values).to.eql([3, 5]);
});

it('injects as values that can be resolved synchronously', () => {
server.bind('my-controller').toClass(MyControllerWithValues);
const inst = server.getSync<MyControllerWithValues>('my-controller');
expect(inst.values).to.eql([3, 5]);
});

it('injects as a view', async () => {
server.bind('my-controller').toClass(MyControllerWithView);
const inst = await server.get<MyControllerWithView>('my-controller');
const view = inst.view;
expect(await view.values()).to.eql([3, 5]);
// Add a new binding that matches the filter
const binding = givenWorkloadMonitor(server, 'server-reporter-2', 7);
// The view picks up the new binding
expect(await view.values()).to.eql([3, 7, 5]);
server.unbind(binding.key);
expect(await view.values()).to.eql([3, 5]);
});

function givenWorkloadMonitors() {
givenServerWithinAnApp();
givenWorkloadMonitor(server, 'server-reporter', 3);
givenWorkloadMonitor(app, 'app-reporter', 5);
}

/**
* Add a workload monitor to the given context
* @param ctx Context object
* @param name Name of the monitor
* @param workload Current workload
*/
function givenWorkloadMonitor(ctx: Context, name: string, workload: number) {
return ctx
.bind(`workloadMonitors.${name}`)
.to(workload)
.tag('workloadMonitor');
}
});

describe('ContextEventObserver', () => {
let contextObserver: MyObserverForControllers;
beforeEach(givenControllerObserver);

it('receives notifications of matching binding events', async () => {
const controllers = await getControllers();
// We have server: ServerController, app: AppController
// NOTE: The controllers are not guaranteed to be ['ServerController`,
// 'AppController'] as the events are emitted by two context objects and
// they are processed asynchronously
expect(controllers).to.containEql('ServerController');
expect(controllers).to.containEql('AppController');
server.unbind('controllers.ServerController');
// Now we have app: AppController
expect(await getControllers()).to.eql(['AppController']);
app.unbind('controllers.AppController');
// All controllers are gone from the context chain
expect(await getControllers()).to.eql([]);
// Add a new controller - server: AnotherServerController
givenController(server, 'AnotherServerController');
expect(await getControllers()).to.eql(['AnotherServerController']);
});

class MyObserverForControllers implements ContextObserver {
controllers: Set<string> = new Set();
filter = filterByTag('controller');
observe(event: ContextEventType, binding: Readonly<Binding<unknown>>) {
if (event === 'bind') {
this.controllers.add(binding.tagMap.name);
} else if (event === 'unbind') {
this.controllers.delete(binding.tagMap.name);
}
}
}

function givenControllerObserver() {
givenServerWithinAnApp();
contextObserver = new MyObserverForControllers();
server.subscribe(contextObserver);
givenController(server, 'ServerController');
givenController(app, 'AppController');
}

function givenController(ctx: Context, controllerName: string) {
class MyController {
name = controllerName;
}
ctx
.bind(`controllers.${controllerName}`)
.toClass(MyController)
.tag('controller', {name: controllerName});
}

async function getControllers() {
return new Promise<string[]>(resolve => {
// Wrap it inside `setImmediate` to make the events are triggered
setImmediate(() => resolve(Array.from(contextObserver.controllers)));
});
return (await viewOfControllers.values()).map((v: any) => v.name);
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {expect} from '@loopback/testlab';
import {Context, ContextView, filterByTag, Getter, inject} from '../..';

let app: Context;
let server: Context;

describe('@inject.* to receive multiple values matching a filter', async () => {
const workloadMonitorFilter = filterByTag('workloadMonitor');
beforeEach(givenWorkloadMonitors);

it('injects as getter', async () => {
class MyControllerWithGetter {
@inject.getter(workloadMonitorFilter)
getter: Getter<number[]>;
}

server.bind('my-controller').toClass(MyControllerWithGetter);
const inst = await server.get<MyControllerWithGetter>('my-controller');
const getter = inst.getter;
expect(await getter()).to.eql([3, 5]);
// Add a new binding that matches the filter
givenWorkloadMonitor(server, 'server-reporter-2', 7);
// The getter picks up the new binding
expect(await getter()).to.eql([3, 7, 5]);
});

describe('@inject', () => {
class MyControllerWithValues {
constructor(
@inject(workloadMonitorFilter)
public values: number[],
) {}
}

it('injects as values', async () => {
server.bind('my-controller').toClass(MyControllerWithValues);
const inst = await server.get<MyControllerWithValues>('my-controller');
expect(inst.values).to.eql([3, 5]);
});

it('injects as values that can be resolved synchronously', () => {
server.bind('my-controller').toClass(MyControllerWithValues);
const inst = server.getSync<MyControllerWithValues>('my-controller');
expect(inst.values).to.eql([3, 5]);
});
});

it('injects as a view', async () => {
class MyControllerWithView {
@inject.view(workloadMonitorFilter)
view: ContextView<number[]>;
}

server.bind('my-controller').toClass(MyControllerWithView);
const inst = await server.get<MyControllerWithView>('my-controller');
const view = inst.view;
expect(await view.values()).to.eql([3, 5]);
// Add a new binding that matches the filter
const binding = givenWorkloadMonitor(server, 'server-reporter-2', 7);
// The view picks up the new binding
expect(await view.values()).to.eql([3, 7, 5]);
server.unbind(binding.key);
expect(await view.values()).to.eql([3, 5]);
});

function givenWorkloadMonitors() {
givenServerWithinAnApp();
givenWorkloadMonitor(server, 'server-reporter', 3);
givenWorkloadMonitor(app, 'app-reporter', 5);
}

/**
* Add a workload monitor to the given context
* @param ctx Context object
* @param name Name of the monitor
* @param workload Current workload
*/
function givenWorkloadMonitor(ctx: Context, name: string, workload: number) {
return ctx
.bind(`workloadMonitors.${name}`)
.to(workload)
.tag('workloadMonitor');
}
});

function givenServerWithinAnApp() {
app = new Context('app');
server = new Context(app, 'server');
}
Loading

0 comments on commit 9200f41

Please sign in to comment.