InversifyJS
InversifyJS copied to clipboard
decorate(injectable(), EventEmitter)) fails with Jest
decorate(injectable(), EventEmitter)) fails in Jest test environment, but works fine when running typescript code using ts-node.
Expected Behavior
When injecting a component that extends EventEmitter (thus requiring decorate(injectable(), EventEmitter))), the component should be injected correctly.
Current Behavior
This works fine when running with ts-node src/event-emitter-inversify/run.ts. It fails when running ts-node ./node_modules/.bin/jest src/event-emitter-inversify/__tests__/event-emitter-inversify.test.ts.
The error is: TypeError: Cannot read property 'constructor' of null
at
at getClassPropsAsTargets (node_modules/inversify/lib/planning/reflection_utils.js:82:75)
Possible Solution
When running the code outside of a test environment I see inversify stepping into relection_utils.getClassPropsAsTargets().
It does this recursively from the child class extending EventEmitter, then into EventEmitter. At the EventEmitter level when we call Object.getPrototypeOf(constructorFunc.prototype).constructor and function Object() is returned and the recursion stops. Indeed the constructor is an Object.
However when running the code in Jest, the same thing happens and at the EventEmitter level, Object.getPrototypeOf(constructorFunc.prototype).constructor returns function Object() as well. However the if (baseConstructor !== Object) test passes and we continue recursing until we bottom out and get the error TypeError: Cannot read property 'constructor' of null.
Steps to Reproduce (for bugs)
run.ts
import { Container } from 'inversify';
import { EventEmitterChild, EventEmitterModule } from './event-emitter-inversify';
import { EventEmitterDependent } from './event-emitter-inversify-dependent';
const referenceContainer = new Container();
referenceContainer.load(EventEmitterModule);
const eventEmitterChild = referenceContainer.get<EventEmitterChild>('EventEmitterChild');
eventEmitterChild.greet();
const eventEmitterDependent = referenceContainer.get<EventEmitterDependent>('EventEmitterDependent');
eventEmitterDependent.run();
event-emitter-inversify.ts
import 'reflect-metadata';
import { EventEmitter } from 'events';
import { ContainerModule, decorate, injectable, interfaces } from 'inversify';
import { EventEmitterDependent } from './event-emitter-inversify-dependent';
decorate(injectable(), EventEmitter);
@injectable()
export class EventEmitterChild extends EventEmitter {
constructor() {
super();
}
greet() {
console.log('hello world.');
}
}
export const EventEmitterModule = new ContainerModule(
(bind: interfaces.Bind) => {
bind<EventEmitterChild>('EventEmitterChild').to(EventEmitterChild).inSingletonScope();
bind<EventEmitterDependent>('EventEmitterDependent').to(EventEmitterDependent).inSingletonScope();
}
);
event-emitter-inversify-depdendent.ts
import { inject, injectable } from 'inversify';
import { EventEmitterChild } from './event-emitter-inversify';
@injectable()
export class EventEmitterDependent {
constructor(@inject('EventEmitterChild') private readonly _eventEmitterChild: EventEmitterChild) {
}
run() {
this._eventEmitterChild.greet();
}
}
tests/event-emitter-inversify.test.ts
import { Container } from 'inversify';
import { EventEmitterChild, EventEmitterModule } from '../event-emitter-inversify';
describe('EventEmitter test', () => {
const container: Container = new Container();
beforeAll(async () => {
container.load(EventEmitterModule);
});
test('get EventEmitterChild from container', () => {
const eventEmitterChild = container.get<EventEmitterChild>('EventEmitterChild');
expect(eventEmitterChild).toBeDefined();
});
});
Your Environment
Mac OSX 10.14 ts-node v6.1.1 node v10.0.0 typescript v2.9.1
Stack trace
TypeError: Cannot read property 'constructor' of null
at getClassPropsAsTargets (node_modules/inversify/lib/planning/reflection_utils.js:82:75)
at getClassPropsAsTargets (node_modules/inversify/lib/planning/reflection_utils.js:84:27)
at getClassPropsAsTargets (node_modules/inversify/lib/planning/reflection_utils.js:84:27)
at getTargets (node_modules/inversify/lib/planning/reflection_utils.js:28:27)
at Object.getDependencies (node_modules/inversify/lib/planning/reflection_utils.js:12:19)
at node_modules/inversify/lib/planning/planner.js:106:51
at Array.forEach (<anonymous>)
at _createSubRequests (node_modules/inversify/lib/planning/planner.js:94:20)
at Object.plan (node_modules/inversify/lib/planning/planner.js:136:9)
at node_modules/inversify/lib/container/container.js:318:37
I am also having the same issue with a class that extends EventEmitter
As a workaround, I'm no longer extending EventEmitter, but including an event emitter as an instance property, and manually creating proxy methods to call that event emitter.
Yeah, that seems to be the best workaround right now. Dislike changing design just to make tests work due to a bug, but it's effective. :wink:
I am having the same problem in this environment:
- Mac OSX 10.13.6
- node 10.15.0
- typescript 3.2.4
- jest 23.6.0 and 24.0.0
^ do you have an idea when this may be fixed?
The same issue:
- Windows 10
- TypeScript 3.3
- jest 20.0.0
Does feel a bit weird that have to decorate these 3rd party classes at all, no? Can't it just assume the defaults of whatever decorate(injectable(), ThirdPartyClass) does ?
What's the reason for this?
Any ideas why this happens? I'm running into the same issue, but instead of it occurring in tests it happens in a Nuxt.js app. Why is it that there are 2 versions of Object() that are incompatible?
EDIT: Never mind, this helped https://github.com/nuxt/nuxt.js/issues/6076
Is there any solution for this ? I am facing same issue in jest
Doing this, at the top of a test file, "fixes" the problem:
const { EventEmitter } = require('events');
Object.getPrototypeOf(EventEmitter.prototype).constructor = Object;
But It feels a bit hacky 😅
I think this could be fixed by the following change in reflection_utils:
baseConstructor !== Object.prototype.constructor