Skip to content

Commit d376cd6

Browse files
test: add tests for async vs sync handlers
1 parent 0b61942 commit d376cd6

8 files changed

+237
-0
lines changed
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { AsyncHandlersAppModule } from '../src/sync-event-handlers/async-handlers-app.module';
3+
import { EventBus } from '../../src/event-bus';
4+
import { HeroKilledDragon2SlowHandler } from '../src/sync-event-handlers/events/handlers/hero-killed-dragon2-slow.handler';
5+
import { HeroKilledDragonSlowHandler } from '../src/sync-event-handlers/events/handlers/hero-killed-dragon-slow.handler';
6+
import { HeroFoundItemSlowHandler } from '../src/sync-event-handlers/events/handlers/hero-found-item-slow.handler';
7+
import { SyncHandlersAppModule } from '../src/sync-event-handlers/sync-handlers-app.module';
8+
import { HeroFoundItemEvent } from '../src/heroes/events/impl/hero-found-item.event';
9+
import { HeroKilledDragonEvent } from '../src/heroes/events/impl/hero-killed-dragon.event';
10+
11+
describe('Sync event handlers', () => {
12+
let moduleRef: TestingModule;
13+
let eventBus: EventBus;
14+
let heroFoundItemHandler: HeroFoundItemSlowHandler;
15+
let heroFoundItemEnded: jest.SpyInstance;
16+
let heroKilledDragonHandler: HeroKilledDragonSlowHandler;
17+
let heroKilledDragonEnded: jest.SpyInstance;
18+
let heroKilledDragon2Handler: HeroKilledDragon2SlowHandler;
19+
let heroKilledDragon2Ended: jest.SpyInstance;
20+
21+
beforeEach(() => {
22+
heroFoundItemEnded.mockClear();
23+
heroKilledDragonEnded.mockClear();
24+
heroKilledDragon2Ended.mockClear();
25+
});
26+
27+
describe('when event handlers are fully asynchronous', () => {
28+
beforeAll(async () => {
29+
moduleRef = await Test.createTestingModule({
30+
imports: [AsyncHandlersAppModule],
31+
}).compile();
32+
33+
await moduleRef.init();
34+
eventBus = moduleRef.get(EventBus);
35+
heroFoundItemHandler = moduleRef.get(HeroFoundItemSlowHandler);
36+
heroKilledDragonHandler = moduleRef.get(HeroKilledDragonSlowHandler);
37+
heroKilledDragon2Handler = moduleRef.get(HeroKilledDragon2SlowHandler);
38+
heroFoundItemEnded = jest.spyOn(heroFoundItemHandler, 'end');
39+
heroKilledDragonEnded = jest.spyOn(heroKilledDragonHandler, 'end');
40+
heroKilledDragon2Ended = jest.spyOn(heroKilledDragon2Handler, 'end');
41+
});
42+
43+
describe('when "HeroFoundItemEvent" event is published', () => {
44+
it('should wait for the event handlers to complete', async () => {
45+
const event = new HeroFoundItemEvent('hero1', 'item1');
46+
47+
await eventBus.publish(event);
48+
49+
expect(heroFoundItemEnded).not.toHaveBeenCalled();
50+
expect(heroKilledDragonEnded).not.toHaveBeenCalled();
51+
expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
52+
});
53+
});
54+
55+
describe('when "HeroKilledDragonEvent" event is published', () => {
56+
it('should wait for the event handlers to complete', async () => {
57+
const event = new HeroKilledDragonEvent('hero1', 'dragon1');
58+
59+
await eventBus.publish(event);
60+
61+
expect(heroFoundItemEnded).not.toHaveBeenCalled();
62+
expect(heroKilledDragonEnded).not.toHaveBeenCalled();
63+
expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
64+
});
65+
});
66+
});
67+
68+
describe('when event handlers are synchronous', () => {
69+
beforeAll(async () => {
70+
moduleRef = await Test.createTestingModule({
71+
imports: [SyncHandlersAppModule],
72+
}).compile();
73+
74+
await moduleRef.init();
75+
eventBus = moduleRef.get(EventBus);
76+
heroFoundItemHandler = moduleRef.get(HeroFoundItemSlowHandler);
77+
heroKilledDragonHandler = moduleRef.get(HeroKilledDragonSlowHandler);
78+
heroKilledDragon2Handler = moduleRef.get(HeroKilledDragon2SlowHandler);
79+
heroFoundItemEnded = jest.spyOn(heroFoundItemHandler, 'end');
80+
heroKilledDragonEnded = jest.spyOn(heroKilledDragonHandler, 'end');
81+
heroKilledDragon2Ended = jest.spyOn(heroKilledDragon2Handler, 'end');
82+
});
83+
84+
describe('when "HeroFoundItemEvent" event is published', () => {
85+
it('should wait for the event handlers to complete', async () => {
86+
const event = new HeroFoundItemEvent('hero1', 'item1');
87+
88+
await eventBus.publish(event);
89+
90+
expect(heroFoundItemEnded).toHaveBeenCalledTimes(1);
91+
expect(heroKilledDragonEnded).not.toHaveBeenCalled();
92+
expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
93+
});
94+
});
95+
96+
describe('when "HeroKilledDragonEvent" event is published', () => {
97+
it('should wait for the event handlers to complete', async () => {
98+
const event = new HeroKilledDragonEvent('hero1', 'dragon1');
99+
100+
await eventBus.publish(event);
101+
102+
expect(heroFoundItemEnded).not.toHaveBeenCalled();
103+
expect(heroKilledDragonEnded).toHaveBeenCalledTimes(1);
104+
expect(heroKilledDragon2Ended).toHaveBeenCalledTimes(1);
105+
});
106+
});
107+
108+
describe('when an unknown event is published', () => {
109+
it('should return directly', async () => {
110+
await eventBus.publish({ id: 'unknown event' });
111+
112+
expect(heroFoundItemEnded).not.toHaveBeenCalled();
113+
expect(heroKilledDragonEnded).not.toHaveBeenCalled();
114+
expect(heroKilledDragon2Ended).not.toHaveBeenCalled();
115+
});
116+
});
117+
});
118+
119+
afterAll(async () => {
120+
await moduleRef.close();
121+
});
122+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from '@nestjs/common';
2+
import { CqrsModule } from '../../../src';
3+
import { EventHandlers } from './events/handlers';
4+
5+
@Module({
6+
imports: [CqrsModule.forRoot()],
7+
providers: [...EventHandlers],
8+
})
9+
export class AsyncHandlersAppModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Logger } from '@nestjs/common';
2+
import { EventsHandler, IEventHandler } from '../../../../../src';
3+
import { waitImmediate } from '../../../../utils/wait-immediate';
4+
import { HeroFoundItemEvent } from '../../../heroes/events/impl/hero-found-item.event';
5+
6+
@EventsHandler(HeroFoundItemEvent)
7+
export class HeroFoundItemSlowHandler
8+
implements IEventHandler<HeroFoundItemEvent>
9+
{
10+
async handle(event: HeroFoundItemEvent) {
11+
await waitImmediate();
12+
Logger.log('HeroFoundItemEvent...', event);
13+
this.end();
14+
}
15+
16+
end() {}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Logger } from '@nestjs/common';
2+
import { EventsHandler, IEventHandler } from '../../../../../src';
3+
import { waitImmediate } from '../../../../utils/wait-immediate';
4+
import { HeroKilledDragonEvent } from '../../../heroes/events/impl/hero-killed-dragon.event';
5+
6+
@EventsHandler(HeroKilledDragonEvent)
7+
export class HeroKilledDragonSlowHandler
8+
implements IEventHandler<HeroKilledDragonEvent>
9+
{
10+
async handle(event: HeroKilledDragonEvent) {
11+
await waitImmediate();
12+
Logger.log('HeroKilledDragonEvent...', event);
13+
this.end();
14+
}
15+
16+
end() {}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Logger } from '@nestjs/common';
2+
import { EventsHandler, IEventHandler } from '../../../../../src';
3+
import { waitImmediate } from '../../../../utils/wait-immediate';
4+
import { HeroKilledDragonEvent } from '../../../heroes/events/impl/hero-killed-dragon.event';
5+
6+
@EventsHandler(HeroKilledDragonEvent)
7+
export class HeroKilledDragon2SlowHandler
8+
implements IEventHandler<HeroKilledDragonEvent>
9+
{
10+
async handle(event: HeroKilledDragonEvent) {
11+
await waitImmediate();
12+
Logger.log('HeroKilledDragonEvent 2...', event);
13+
this.end();
14+
}
15+
16+
end() {}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { HeroKilledDragonSlowHandler } from './hero-killed-dragon-slow.handler';
2+
import { HeroFoundItemSlowHandler } from './hero-found-item-slow.handler';
3+
import { HeroKilledDragon2SlowHandler } from './hero-killed-dragon2-slow.handler';
4+
5+
export const EventHandlers = [
6+
HeroKilledDragonSlowHandler,
7+
HeroKilledDragon2SlowHandler,
8+
HeroFoundItemSlowHandler,
9+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Module, OnModuleInit } from '@nestjs/common';
2+
import { CqrsModule, EventBus, IEvent } from '../../../src';
3+
import { EventHandlers } from './events/handlers';
4+
import { SyncPublisher } from './sync-publisher';
5+
6+
@Module({
7+
imports: [
8+
CqrsModule.forRoot({
9+
eventPublisher: new SyncPublisher(),
10+
}),
11+
],
12+
providers: [...EventHandlers],
13+
})
14+
export class SyncHandlersAppModule implements OnModuleInit {
15+
constructor(private readonly eventBus: EventBus) {}
16+
17+
onModuleInit() {
18+
(this.eventBus.publisher as SyncPublisher<IEvent>).setEventOperators(
19+
this.eventBus.eventOperators,
20+
);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { lastValueFrom, map, merge, of } from 'rxjs';
2+
import { IEvent, IEventPublisher } from '../../../src/interfaces';
3+
import { EventOperator } from '../../../src/event-bus';
4+
5+
export class SyncPublisher<EventBase extends IEvent>
6+
implements IEventPublisher<EventBase>
7+
{
8+
private eventOperators: EventOperator<EventBase>[] = [];
9+
10+
async publish<T extends EventBase>(event: T) {
11+
const event$ = of(event);
12+
13+
await lastValueFrom(
14+
merge(
15+
event$.pipe(map(() => undefined)),
16+
...this.eventOperators.map((operator) => event$.pipe(operator)),
17+
),
18+
);
19+
}
20+
21+
setEventOperators<T extends EventBase>(eventOperators: EventOperator<T>[]) {
22+
this.eventOperators = eventOperators;
23+
}
24+
}

0 commit comments

Comments
 (0)