Home > front end >  Mocking es6 class static methods with Jest
Mocking es6 class static methods with Jest

Time:02-03

I am really having big trouble handling this issue. I have read Jest docs a lot, also other articles, but nothing has helped me yet, even chatGPT.

I am trying to test this class.

export class CookiesManager extends Auth {
  static get(key: string) {
    return this.cookies.get(key);
  }

  static set(key: string, value: any, config?: CookieSetOptions) {
    this.cookies.set(key, value, config || this.config);
  }

  static remove(key: string) {
    const { hostname } = new URL(`${process.env.WEB_HOST}`);

    this.cookies.remove(key, { ...this.config, domain: hostname });
  }
}

Here is the Auth class, but in my case, I haven't even reached here or I guess I will not need to handle that part in the scope of this one. Anyway, just for a reference.

import moment from 'moment';
import Cookies, { CookieSetOptions } from 'universal-cookie';

export class Auth {
  static cookies = new Cookies();

  static config: CookieSetOptions = {
    path: '/',
    maxAge: 1800,
    expires: moment().add(30, 'm').toDate(),
    secure: process.env.NODE_ENV !== 'development',
  };

  static setToken(token: string) {
    token && this.cookies.set('auth_token', token, this.config);
  }

  static getToken() {
    return this.cookies.get('auth_token');
  }

  static clear() {
    this.cookies.remove('auth_token', this.config);
  }
}

I have written the mock for the CookiesManager class.The module has also other named exports.

jest.mock('core/utils/storage-manager.ts', () => {
      const originalClasses = jest.requireActual('core/utils/storage-manager.ts');
    
      class CookiesManagerMock {
        static config = {
          path: '/',
          maxAge: 1800,
        }
    
        static set = jest.fn().mockImplementation((key, value, config) => {
          console.log('Mock set called');
          mockCookies[key] = value;
        })
    
        static get = jest.fn().mockImplementation((key) => {
          console.log('Mock get called');
          return mockCookies[key];
        })
    
        static remove = jest.fn().mockImplementation((key, config) => {
          console.log('Mock remove called');
          delete mockCookies[key];
        })
      }
    
      return {
        ...originalClasses,
        CookiesManager: CookiesManagerMock,
      }
    })

This is the test block.

describe('CookiesManager', () => {
    afterEach(() => {
      jest.restoreAllMocks();
    });

    test('It should set a cookie', () => {
      CookiesManager.set('test_key', 'test_value');
      console.log(mockCookies.test_key, 'mockCookies')
      expect(CookiesManager.set).toHaveBeenCalledWith('test_key', 'test_value');
    });

    test('It should get a cookie', () => {
      CookiesManager.get.mockReturnValueOnce('test_value');
      expect(CookiesManager.get('test_key')).toEqual('test_value');
      expect(CookiesManager.get).toHaveBeenCalledWith('test_key');
    });

    test('It should remove a cookie', () => {
      CookiesManager.remove('test_key');
      expect(CookiesManager.remove).toHaveBeenCalledWith('test_key');
    });
})

UPDATED

Even though the tests are passing, non of the console.log statements are being called in mocked class. And also mockCookies is always empty. I have tried the same with CommonJS modules and the strange thing is that console.log statements are being called.

In Jest docs, it's clearly stated.

mockFn.mockImplementation(fn)

Accepts a function that should be used as the implementation of the mock. The mock itself will still record all calls that go into and instances that come from itself – the only difference is that the implementation will also be executed when the mock is called.

Maybe I am getting something wrong and don't understand the nature of the mocks.

CodePudding user response:

You didn't mock the static methods correctly. The way you are using is trying to mock the instance methods of a class. Here is a solution, create a mock CookiesManager class with mock static methods.

storage-manager.ts:

type CookieSetOptions = any;

export class CookiesManager {
  static get(key: string) {}

  static set(key: string, value: any, config?: CookieSetOptions) {}

  static remove(key: string) {}
}

export const a = () => 'a'
export const b = () => 'b'

storage-manager.test.ts:

import { CookiesManager, a, b } from './storage-manager';

jest.mock('./storage-manager', () => {
  const originalModules = jest.requireActual('./storage-manager');

  const mockCookies = {};
  class CookiesManagerMock {
    static set = jest.fn().mockImplementation((key, value, config) => {
      console.log('Mock set called');
      mockCookies[key] = value;
    });

    static get = jest.fn().mockImplementation((key) => {
      console.log('Mock get called');
      return mockCookies[key];
    });

    static remove = jest.fn().mockImplementation((key, config) => {
      console.log('Mock remove called');
      delete mockCookies[key];
    });
  }
  return {
    ...originalModules,
    CookiesManager: CookiesManagerMock,
  };
});

describe('75323871', () => {
  test('It should set a cookie', () => {
    const config = { path: '/', maxAge: 1800 };
    expect(jest.isMockFunction(CookiesManager.set)).toBeTruthy();
    CookiesManager.set('test_key', 'test_value', config);
    expect(CookiesManager.set).toHaveBeenCalledWith('test_key', 'test_value', config);
    expect(CookiesManager.get('test_key')).toBe('test_value');

    // other named exports
    expect(a()).toBe('a');
    expect(b()).toBe('b');
  });
});

jest.config.js:

module.exports = {
  preset: 'ts-jest/presets/js-with-ts'
};

Test result:

 PASS  stackoverflow/75323871/storage-manager.test.ts (8.614 s)
  75323871
    ✓ It should set a cookie (17 ms)

  console.log
    Mock set called

      at Function.<anonymous> (stackoverflow/75323871/storage-manager.test.ts:9:15)

  console.log
    Mock get called

      at Function.<anonymous> (stackoverflow/75323871/storage-manager.test.ts:14:15)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        9.358 s, estimated 10 s

Note: Maybe jest.spyOn(CookiesManager, 'set').mockImplementation() is a better solution, you only need to mock the specific static method. see https://jestjs.io/docs/es6-class-mocks#static-getter-and-setter-methods

package version:

"jest": "^26.6.3",
"ts-jest": "^26.4.4"
  • Related