Reputation: 652
I'm having trouble trying to mock a class and a constructor.
I have a App.ts class that I want to test:
class App {
public server: Express;
constructor() {
this.server = new Express();
this.server.init();
}
}
export default App;
For the test scenario -> Once I instanciate a App class, it should : - Make sure a new instance of the class Express is made - Make sure a call to the init function is made
So I have my App.test file :
import App from '../App';
let mockedExp: jest.Mock;
jest.mock('../Express', () => {
return {
default: jest.fn().mockImplementation(() => {
return {
init: mockedExp,
};
}),
};
});
describe('App', () => {
beforeEach(() => {
mockedExp = jest.fn().mockImplementation();
mockedExp.mockClear();
});
it('Should call init from express with initialize', () => {
new App();
expect(mockedExp).toBeCalled();
});
});
When I run the test, I'm getting the following :
TypeError: Express_1.default is not a constructor
8 |
9 | constructor() {
> 10 | this.server = new Express();
| ^
11 | this.server.init();
12 | }
Express class :
import express from 'express';
import Boom from 'express-boom';
import morgan from 'morgan';
import Locals from './Locals';
import Middleware from './Middleware';
import Logger from './Logger';
export default class Express {
public app: express.Application;
constructor() {
this.app = express();
// disable the x-powered-by header
this.app.disable('x-powered-by');
this.app.locals = Locals.getConfig();
// add boom
this.app.use(Boom());
this.app.set('logger', Logger);
// morgan logger
/* instanbul ignore next */
if (this.app.locals.env === 'production') this.app.use(morgan('combined'));
else {
this.app.use(morgan('dev'));
}
}
public init(): void {
const mid = new Middleware(this.app);
mid.addLogRoutes();
}
public start(): void {
const server = this.app.listen(this.app.locals.PORT, (error: Error) => {
if (error) {
throw new Error(`Unable to start server ${error}`);
}
/* istanbul ignore next */
console.log(`Server starting on ${server.address().port}`);
});
}
}
I'm using the following typescript rules :
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true,
"strictFunctionTypes": true /* Enable strict checking of function types. */,
"strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */
So what am I doing wrong ?
Upvotes: 6
Views: 10195
Reputation: 744
For me just adding "esModuleInterop": true
to the ts config of my specs resolved the issue.
But in my use case, i didn't even tried to mock an external library, I wanted my tests to run with it. Here is an example of my authored code:
import { default as AutoNumeric } from 'autonumeric';
// was failing here
const options = (AutoNumeric as any).options;
class A {
instance: AutoNumeric;
defaultCurrencySymbol = options.currencySymbol.none;
constructor() {
// failing here as well
this.instance = new AutoNumeric(...)
}
}
Upvotes: 0
Reputation: 3230
You have to specify __esModule: true
in the returned object
jest.mock('../Express', () => {
return {
__esModule: true,
default: jest.fn().mockImplementation(() => {
return {
init: mockedExp,
};
}),
};
});
Alternatively, if the default export is the only export, it is possible to return it from factory directly:
jest.mock('../Express', () => function() { // arrow function cannot be invoked with `new`
return { init: mockedExp };
});
// or, if you want to spy on the constructor
jest.mock('../Express', () => jest.fn().mockImplementation(() => ({
init: mockedExp
})));
Upvotes: 20