Mock imported class in typescript with jest

18,703

Issue

The typing for AuthenticationService does not include the mock property so TypeScript throws an error.


Details

jest.mock creates an automatic mock of the module which "replaces the ES6 class with a mock constructor, and replaces all of its methods with mock functions that always return undefined".

In this case the default export of authentication.service.ts is an ES6 class so it is replaced with a mock constructor.

The mock constructor has a mock property but TypeScript doesn't know about it and is still treating AuthenticationService as the original type.


Solution

Use jest.Mocked to let TypeScript know about the typing changes caused by jest.mock:

import * as original from './services/implementation/authentication.service';  // import module
jest.mock('./services/implementation/authentication.service');

const mocked = original as jest.Mocked<typeof original>;  // Let TypeScript know mocked is an auto-mock of the module
const AuthenticationService = mocked.default;  // AuthenticationService has correct TypeScript typing

beforeEach(() => {
  AuthenticationService.mockClear();
});

it('test', () => {

    // mock.instances is available with automatic mocks:
    const authServerInstance = AuthenticationService.mock.instances[0];
Share:
18,703
devran
Author by

devran

Updated on June 11, 2022

Comments

  • devran
    devran almost 2 years

    I am trying to use jest to mock an imported class inside a typescript class, the following code is used for the main program(I removed some code from inside the functions but it shoud still be clear what I am trying to do)

    import * as SocketIO from "socket.io";
    
    import {AuthenticatedDao} from "../../dao/authenticated.dao";
    
    export default class AuthenticationService {
        private readonly _authenticatedDao: AuthenticatedDao = AuthenticatedDao.Instance;
        private readonly _io;
    
        constructor(socketIo: SocketIO.Server) {
            this._io = socketIo;
        }
    
        public authenticateUser(username: string, password: string, clientSocketId: string): void {
            this._authenticatedDao.authenticateUser(username, password).then((authenticatedUser) => {
    
            }).catch(rejected => {
    
            });
        }
    }
    
    
    import {createServer, Server} from 'http';
    import * as express from 'express';
    import * as socketIo from 'socket.io';
    import {LogincredentialsDto} from "./models/dto/logincredentials.dto";
    import {config} from './config/config';
    import AuthenticationService from "./services/implementation/authentication.service";
    import {Logger} from "./helperclasses/logger";
    import {format} from "util";
    
    export class ClassA {
        private readonly _configPort = config.socketServerPort;
    
        private readonly _logger: Logger = Logger.Instance;
        private _app: express.Application;
        private _server: Server;
        private _io: socketIo.Server;
        private _socketServerPort: string | number;
        private _authenticationService: AuthenticationService;
    
    
        constructor() {
            this.configure();
            this.socketListener();
        }
    
        private configure(): void {
            this._app = express();
    
            //this._server = createServer(config.sslCredentials, this._app);
            this._server = createServer(this._app);
    
            this._socketServerPort = process.env.PORT || this._configPort;
            this._io = socketIo(this._server);
    
            this._server.listen(this._socketServerPort, () => {
                this._logger.log(format('Server is running on port: %s', this._socketServerPort));
            });
    
            this._authenticationService = new AuthenticationService(this._io);
        }
    
    
        private socketListener(): void {
            this._io.on('connection', (client) => {
                    client.on('authenticate', (loginCreds: LogincredentialsDto) => {
                        console.log(loginCreds.username, loginCreds.password, client.id);
                        this._authenticationService.authenticateUser(loginCreds.username, loginCreds.password, client.id);
                    });
                }
            );
        }
    }
    

    I am trying to mock the function "authenticateUser" in "AuthenticationService", instead of calling the normal code I want to mock the promise. I tried using the examples provided in https://jestjs.io/docs/en/es6-class-mocks but when I try doing the following:

    import AuthenticationService from '../src/services/implementation/authentication.service';
    jest.mock('./services/implementation/authentication.service');
    
    beforeEach(() => {
        AuthenticationService.mockClear();
    });
    
    it('test', () => {
    
        // mock.instances is available with automatic mocks:
        const authServerInstance = AuthenticationService.mock.instances[0];
    

    I get this error: Error:(62, 31) TS2339: Property 'mock' does not exist on type 'typeof AuthenticationService'.

    What am I doing wrong here? Should I be mocking the class/function differently since it's using promises?

  • devran
    devran over 5 years
    Thank you for your help, by using your code I figured out how to mock a function within a class, thanks for including the details as well, it makes it more clear now.
  • Mithir
    Mithir about 5 years
    @brian-lives-outdoors I tried this but for some reason typescript still looks at the Mocked instance as the actual type it is mocking... any ideas? (trying to mock an interface)
  • Chris Watts
    Chris Watts almost 5 years
    This actually doesn't work for me because TypeScript is telling me I need to cast it to unknown first. i.e. original as unknown as jest.Mocked<typeof original> Does anyone know a way around this?
  • rsan
    rsan almost 5 years
    This isn't working for me. AuthenticationService.mockClear(); produces -> property mockClear does not exist in bla bla. Inspecting the object it have mockClear, mock, etc. But the types are not known to typescript.
  • Stefan Frye
    Stefan Frye about 4 years
    I had to replace const AuthenticationService = mocked.default; with const AuthenticationService = mocked.AuthenticationService, then it worked for me. I'm using ts-jest 25.2.1.
  • Alexey Sh.
    Alexey Sh. almost 3 years
    TS2339: Property 'default' does not exist on type 'Mocked '.
  • Lee Goddard
    Lee Goddard about 2 years
    Property 'Mocked' does not exist on type 'typeof jest'. Did you mean 'mocked'?ts(2551)