Mocking es6 class static methods with Jest - javascript

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.

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

Unable to properly mock API calls using Jest

I have been trying to mock my apis using jest.mock() but I cant even get jest to call these apis normally.
I have been able to successfully mock the the function and then mock the api like this:
wrapper.vm.getUser = jest.fn(() => Promise.resolve({}));
but I would like to mock all the apis in api/authApis.js directly instead of intercepting them at the function.
I cant do it because this.$apis is showing up as undefined.
How do I access and call this.$apis.getUser() using Jest?
tests/header.spec.js
import { mount } from "#vue/test-utils";
import Header from "../components/Header.vue";
describe("Header", () => {
it("returns the val from api", async () => {
//setting multiUser true so button is visible
const multiUsers = true;
const wrapper = mount(Header, {
propsData: {
multiUsers
}
});
console.log(wrapper.vm.getUser());
const changeUsrBtn = wrapper.find("#btnChangeUser");
//triggering the user btn with a click
changeUsrBtn.trigger("click");
expect(wrapper.find("button").text()).toBe("Change User");
expect(wrapper.vm.getUser()).toHaveReturned();
});
});
The error:
FAIL __tests__/header.spec.js
Header
× returns the val from api (122 ms)
● Header › returns the val from api
expect(received).toHaveReturned()
Matcher error: received value must be a mock function
Received has type: object
Received has value: {}
23 | expect(wrapper.find("button").text()).toBe("Change User");
24 |
> 25 | expect(wrapper.vm.getUser()).toHaveReturned();
| ^
26 |
27 | });
28 | });
at Object.<anonymous> (__tests__/header.spec.js:25:36)
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:387:19)
at _run10000 (node_modules/#jest/core/build/cli/index.js:408:7)
at runCLI (node_modules/#jest/core/build/cli/index.js:261:3)
console.error
TypeError: Cannot read property 'authService' of undefined
components/Header.vue
async getUser() {
this.showUserLoader = true;
try {
const {
data: { userList }
} = await
this.$apis.authService.getUser();
this.$store.dispatch("auth/updateUsers", UserList);
} catch (error) {
console.error(error);
} finally {
this.showUserLoader = false;
}
}
...
api/authApis.js (I would like to mock this entire file):
export default $axios => ({
getUser() {
return $axios({
url_instance: authInstance,
method: "get",
url_suffix: "/sc/typesOfUser",
});
},
changeUser() {
return $axios({
url_instance: authInstance,
method: "post",
url_suffix: "/sc/changeUser"
});
},
...
})
It depends on how you set the global this.$api, but if you want to mock an entire file (especially using the default export), you should use the __esModule property:
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
More explanations on Jest mock default and named export.

NestJS accessing private class field before testing method with jest

Assuming there is the following nest service class with the private field myCache and the public method myFunction:
import * as NodeCache from 'node-cache'
class MyService{
private myCache = new NodeCache();
myFunction() {
let data = this.myCache.get('data');
if(data === undefined){
// get data with an http request and store it in this.myCache with the key 'data'
}
return data;
}
}
I want to test the function myFunction for two different cases.
Fist case: If condition is true. Second Case: If condition is false.
Here is the test class with the two missing tests:
import { Test, TestingModule } from '#nestjs/testing';
import { MyService} from './myService';
describe('MyService', () => {
let service: MyService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MyService],
}).compile();
service = module.get<MyService>(MyService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('myFunction', () => {
it('should return chached data', () => {
// first test
}),
it('should return new mocked data', () => {
// second test
})
})
});
Therefore I guess I have to access or mock the myCache private class field.
Because it is private I can't access it in the test class.
My Question is: What's the best and correct way to achieve this?
If you're just looking to mock it, you can always use as any to tell Typescript to not warn you about accessing private values.
jest.spyOn((service as any).myCache, 'get').mockReturnValueOnce(someValue);
However, that's kind of annoying to have to do over and over again and not really the best practice. What I would do instead is move your cache to be an injectable provider so that it could be swapped out at a moments notice and your MyService no longer has a hard dependency on node-cache. Something like this:
// my.module.ts
#Module({
providers: [
MyService,
{
provide: 'CACHE',
useClass: NodeCache
}
]
})
export class MyModule {}
// my.service.ts
#Injectable()
export class MyService {
constructor(#Inject('CACHE') private readonly myCache: NodeCache) {}
...
And now in your test you can swap out the CACHE token for a mock implementation that can also be retrieved in your beforeEach block, meaning no more any.
describe('MyService', () => {
let service: MyService;
let cache: { get; set; }; // you can change the type here
beforeEach(async () => {
const modRef = await Test.createTestingModule({
providers: [
MyService,
{
provide: 'CACHE',
useValue: { get: jest.fn(), set: jest.fn() }
}
]
}).compile();
service = modRef.get(MyService);
cache = modRef.get<{ get; set; }>('CACHE');
});
});
And now you can call jest.spyOn(cache, 'get') without the use of as any.

Start and stop server with supertest

I have the following server class :
import express, { Request, Response } from 'express';
export default class Server {
server: any;
exp: any;
constructor() {
this.exp = express();
this.exp.get('/', (_req: Request, res: Response) => {
res.json('works');
});
}
start(): void {
this.server = this.exp.listen(3000);
}
stop(): void {
this.server.close();
}
}
I'm using supertest for end-to-end testing. I wish to start my application beforeAll tests and stop it when the tests are done.
It's easy to do that using beforAll and afterAll where I can just once instanciate the Server class and call the start and close methods.
But as I have 10+ controllers to test, I want to avoid to start and stop the server during each test file.
I found on the documentation the setupFiles and setupFilesAfterEnv but I can't stop the server since the instance is not "shared" in the two files.
This is an example of 1 test file :
import supertest from 'supertest';
describe('Album Test', () => {
let app: App;
beforeAll(async (done) => {
app = new App();
await app.setUp(); // database connection (not mentionned in the preivous example)
done();
});
afterAll(async (done) => {
await app.close();
app.server.stop();
done();
});
const api = supertest('http://localhost:3000');
it('Hello API Request', async () => {
const result = await api.get('/v1/user');
expect(result.status).toEqual(200);
...
});
});
This works totally fine but I'm duplicating this beforeAll and afterAll methods in every test file. Is there a way to declare it only once ?
Thanks
Try this it works
const supertest = require('supertest')
const app = require('../../app')
describe('int::app', function(){
let request = null
let server = null
before(function(done){
server = app.listen(done)
request = supertest.agent(server)
})
after(function(done){
server.close(done)
})
it('should get /api/v1/laps/85299', function(){
return request.get('/api/v1/laps/85299')
.expect(200, { data: {} })
})
})
You could use setupFiles to set up test fixtures globally. You can assign variables that you want to use in multiple test files to Node.js global object.
E.g.
app.ts:
import express, { Request, Response } from 'express';
export default class Server {
server: any;
exp: any;
constructor() {
this.exp = express();
this.exp.get('/', (_req: Request, res: Response) => {
res.json('works');
});
}
start(): void {
this.server = this.exp.listen(3000);
}
stop(): void {
this.server.close();
}
}
app.setup.js:
const App = require('./app').default;
beforeAll(() => {
global.app = new App();
global.app.exp.set('test setup', 1);
console.log('app setup');
});
afterAll(() => {
console.log('app stop');
});
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'node',
setupFilesAfterEnv: [
'./jest.setup.js',
'/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/61659975/app.setup.js',
],
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
verbose: true,
};
a.controller.test.js:
describe('controller a', () => {
it('should pass', () => {
console.log('test setup:', global.app.exp.get('test setup'));
expect(1 + 1).toBe(2);
});
});
b.controller.test.js:
describe('controller b', () => {
it('should pass', () => {
console.log('test setup:', global.app.exp.get('test setup'));
expect(1 + 1).toBe(2);
});
});
unit test results:
PASS stackoverflow/61659975/a.controller.test.js
controller a
✓ should pass (5ms)
console.log
app setup
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:6:11)
console.log
app setup
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:6:11)
console.log
test setup: 1
at Object.<anonymous> (stackoverflow/61659975/b.controller.test.js:3:13)
console.log
test setup: 1
at Object.<anonymous> (stackoverflow/61659975/a.controller.test.js:3:13)
console.log
app stop
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:10:11)
console.log
app stop
at Object.<anonymous> (stackoverflow/61659975/app.setup.js:10:11)
PASS stackoverflow/61659975/b.controller.test.js
controller b
✓ should pass (3ms)
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.749s, estimated 12s

How to mock axios.create([config]) function to return its instance methods instead of overriding them with mock?

I'm trying to mock axios.create() because I'm using its instance across the app and obviously need all of its implementation which is destroyed by the mock, thus cannot get the result of the get, post method properly.
This is how the code looks like in the actual file:
export const axiosInstance = axios.create({
headers: {
...headers
},
transformRequest: [
function (data, headers) {
return data;
},
],
});
const response = await axiosInstance.get(endpoint);
And here is the mock setup for axios inside the test file
jest.mock('axios', () => {
return {
create: jest.fn(),
get: jest.fn(() => Promise.resolve()),
};
}
);
How could I get all of the instance methods in the axiosInstance variable instead of just having a mock function which does nothing?
Documentation for axios.create and instance methods: https://github.com/axios/axios#instance-methods
You can use jest's genMockFromModule. It will generate jest.fn() for each of the modules's methods and you'll be able to use .mockReturnThis() on create to return the same instance.
example:
./src/__mocks__/axios.js
const axios = jest.genMockFromModule('axios');
axios.create.mockReturnThis();
export default axios;
working example
Edit:
from Jest 26.0.0+ it's renamed to jest.createMockFromModule
Ended up sticking with the axios mock and just pointing to that mock by assigning the axiosInstance pointer to created axios mock.
Basically as #jonrsharpe suggested
Briefly:
import * as m from 'route';
jest.mock('axios', () => {
return {
create: jest.fn(),
get: jest.fn(() => Promise.resolve()),
};
}
);
m.axInstance = axios
Would be very nice though if I could have gone without it.
Since I need to manage Axios instances, I need a way of retrieving the mocks that are created so I can manipulate the responses. Here's how I did it.
import type { AxiosInstance, AxiosStatic } from 'axios';
const mockAxios = jest.createMockFromModule<AxiosStatic>('axios') as jest.Mocked<AxiosStatic>;
let mockAxiosInstances: jest.Mocked<AxiosInstance>[] = [];
mockAxios.create = jest.fn((defaults) => {
const mockAxiosInstance = jest.createMockFromModule<AxiosInstance>(
'axios'
) as jest.Mocked<AxiosInstance>;
mockAxiosInstance.defaults = { ...mockAxiosInstance.defaults, ...defaults };
mockAxiosInstances.push(mockAxiosInstance);
return mockAxiosInstance;
});
export function getMockAxiosInstances() {
return mockAxiosInstances;
}
export function mostRecentAxiosInstanceSatisfying(fn: (a: AxiosInstance) => boolean) {
return mockAxiosInstances.filter(fn).at(-1);
}
export function clearMockAxios() {
mockAxiosInstances = [];
}
export default mockAxios;
I added three additional methods:
clearMockAxios which clears the instance list
getMockAxiosInstances which gets the list of axios instances that are generated by axios.create
mostRecentAxiosInstanceSatisfying is a function that will do a filter to get the most recent axios instance that satisfies the predicate. This is what I generally use in the test case since React may render more than expected (or as expected) and I generally just need the last instance.
I use it as follows:
import { getMockAxiosInstances, mostRecentAxiosInstanceSatisfying, clearMockAxios } from '../../__mocks__/axios';
it("...", () => {
...
const mockAppClient = mostRecentAxiosInstanceSatisfying(
a => a.defaults.auth?.username === "myusername"
);
mockAppClient.post.mockImplementation(() => {
return Promise.resolve({ data: "AAA" })
})
... do something that calls the client ...
});
The following code works!
jest.mock("axios", () => {
return {
create: jest.fn(() => axios),
post: jest.fn(() => Promise.resolve()),
};
});

Jest - mock a named class-export in typescript

I have a node module which exports a few classes, one of which is Client, which I use to create a client (having a few APIs as methods).
I'm trying to test my module which uses this node module as a dependency using Jest. However, I've been unable to successfully mock the one method (say search()) in the Client class.
Here is my spec for myModule:
//index.spec.ts
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
jest.mock('node-module-name');
const mockedClient = <jest.Mock<externalModule.Client>>externalModule.Client;
const myClient = createClient({/*params*/}); //returns instance of Client class present in node module by executing Client() constructor
myClient.searchByName('abc'); //calls search API - I need to track calls to this API
expect(mockedClient).toHaveBeenCalled();
expect(mockedClient.prototype.search).toHaveBeenCalledWith('abc');
});
});
This, however, doesn't create a mock at all and triggers a nock error since the search API tries to connect to the url (given through params).
I've also tried mocking the Client class like the following. While successfully creating a mock for the Client class and also the search API (verified that search() is also mocked through console logs), it gives me an error while I try to check if search() has been called.
externalModule.Client = jest.fn(() => { return { search: jest.fn(() => Promise.resolve('some response')) } });
//creates the mock successfully, but not sure how to track calls to 'search' property
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled(); //Successful
expect(externalModule.Client.prototype.search).toHaveBeenCalled(); //returns error saying "jest.fn() value must be a mock function or spy, Received: undefined"
I'm not sure what I'm doing wrong. Thank you in advance.
Mocking whole module
Try moving jest.mock to the top of file
//index.spec.ts
const search = jest.fn();
jest.mock('node-module-name', () => ({
Client: jest.fn(() => ({ search }))
}));
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
const myClient = createClient({/*params*/});
myClient.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalledWith('abc');
externalModule.Client.mockClear();
search.mockClear();
});
});
Mocking only Client
Create search constant and track it.
const search = jest.fn();
externalModule.Client = jest.fn(() => ({ search }));
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalled();
Here is how I mocked it. I had to change naming and removing some code to avoid exposing original source.
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
Full code.
// foo-client.ts
export class FooClient {
constructor(private config: any)
post() {}
}
// foo-service.ts
import { FooClient } from './foo-client'
export class FooLabelService {
private client: FooClient
constructor() {
this.client = new FooClient()
}
createPost() {
return this.client.post()
}
}
// foo-service-test.ts
import { FooService } from '../foo-service'
const mockPost = jest.fn()
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
describe('FooService', () => {
let fooService: FooService
beforeEach(() => {
jest.resetAllMocks()
fooService = new FooService()
})
it('something should happened', () => {
mockPost.mockResolvedValue()
fooService.createPost()
})
})

Categories

Resources