Mocking up static methods in jest - javascript

I am having trouble mocking up a static method in jest. Immagine you have a class A with a static method:
export default class A {
f() {
return 'a.f()'
}
static staticF () {
return 'A.staticF()'
}
}
And a class B that imports A
import A from './a'
export default class B {
g() {
const a = new A()
return a.f()
}
gCallsStaticF() {
return A.staticF()
}
}
Now you want to mock up A. It is easy to mock up f():
import A from '../src/a'
import B from '../src/b'
jest.mock('../src/a', () => {
return jest.fn().mockImplementation(() => {
return { f: () => { return 'mockedA.f()'} }
})
})
describe('Wallet', () => {
it('should work', () => {
const b = new B()
const result = b.g()
console.log(result) // prints 'mockedA.f()'
})
})
However, I could not find any documentation on how to mock up A.staticF. Is this possible?

You can just assign the mock to the static method
import A from '../src/a'
import B from '../src/b'
jest.mock('../src/a')
describe('Wallet', () => {
it('should work', () => {
const mockStaticF = jest.fn().mockReturnValue('worked')
A.staticF = mockStaticF
const b = new B()
const result = b.gCallsStaticF()
expect(result).toEqual('worked')
})
})

Hope this will help you
// code to mock
export class AnalyticsUtil {
static trackEvent(name) {
console.log(name)
}
}
// mock
jest.mock('../src/AnalyticsUtil', () => ({
AnalyticsUtil: {
trackEvent: jest.fn()
}
}))
// code to mock
export default class Manager {
private static obj: Manager
static shared() {
if (Manager.obj == null) {
Manager.obj = new Manager()
}
return Manager.obj
}
nonStaticFunc() {
}
}
// mock
jest.mock('../src/Manager', () => ({
shared: jest.fn().mockReturnValue({
nonStaticFunc: jest.fn()
})
}))
// usage in code
someFunc() {
RNDefaultPreference.set('key', 'value')
}
// mock RNDefaultPreference
jest.mock('react-native-default-preference', () => ({
set: jest.fn()
}))
// code to mock
export namespace NavigationActions {
export function navigate(
options: NavigationNavigateActionPayload
): NavigationNavigateAction;
}
// mock
jest.mock('react-navigation', () => ({
NavigationActions: {
navigate: jest.fn()
}
}))

I managed to mock it in a separate file in the __mocks__ folder using prototyping. So you would do:
function A() {}
A.prototype.f = function() {
return 'a.f()';
};
A.staticF = function() {
return 'A.staticF()';
};
export default A;

Here's an example with an ES6 import.
import { MyClass } from '../utils/my-class';
const myMethodSpy = jest.spyOn(MyClass, 'foo');
describe('Example', () => {
it('should work', () => {
MyClass.foo();
expect(myMethodSpy).toHaveBeenCalled();
});
});

We need to create a mock and give visibility for the mocked method to the test suite. Below full solution with comments.
let mockF; // here we make variable in the scope we have tests
jest.mock('path/to/StaticClass', () => {
mockF = jest.fn(() => Promise.resolve()); // here we assign it
return {staticMethodWeWantToMock: mockF}; // here we use it in our mocked class
});
// test
describe('Test description', () => {
it('here our class will work', () => {
ourTestedFunctionWhichUsesThisMethod();
expect(mockF).toHaveBeenCalled(); // here we should be ok
})
})

Using Object.assign on the mock constructor allows simultaneous mocking of the class and its static methods. Doing this allows you to achieve the same structure you get when creating a class with static members.
import A from '../src/a'
import B from '../src/b'
jest.mock('../src/a', () =>
Object.assign(
jest.fn(
// constructor
() => ({
// mock instance here
f: jest.fn()
})),
{
// mock static here
staticF: jest.fn(),
}
)
)

Jest Spies
I went with the route of using jest.spyOn.
Example
encryption.ts
export class Encryption {
static encrypt(str: string): string {
// ...
}
static decrypt(str: string): string {
// ...
}
}
property-encryption.spec.ts
import { Encryption } from './encryption'
import { PropertyEncryption } from './property-encryption'
describe('PropertyEncryption', () => {
beforeAll(() => {
jest
.spyOn(Encryption, 'encrypt')
.mockImplementation(() => 'SECRET')
jest
.spyOn(Encryption, 'decrypt')
.mockImplementation(() => 'No longer a secret')
})
it("encrypts object values and retains the keys", () => {
const encrypted = PropertyEncryption.encrypt({ hello: 'world' });
expect(encrypted).toEqual({ hello: 'SECRET' });
});
it("decrypts object values", () => {
const decrypted = PropertyEncryption.decrypt({ hello: "SECRET" });
expect(decrypted).toEqual({ hello: 'No longer a secret' });
});
})

Related

How to mock different hoist module value in jest

I'm trying to test geUrl() function in the file.
The first test case is passing but the second one will fail.
Because isFromApp is called only once on load of the file. I could not find a way to change the mock for it.
Any one know how can I mock different value for isFromApp for the second test case?
util.js
import { fromApp } from 'utils/domUtil';
export const isFromApp = fromApp('query', 'app');
export function getUrl() {
if (isFromApp) return 'is from app';
return 'is from web';
}
util.test.js
import { getUrl } from './util';
jest.mock('utils/domUtil', () => {
const actual = jest.requireActual('utils/domUtil');
return {
...actual,
fromApp: jest.fn().mockReturnValueOnce(true).mockReturnValueOnce(false),
};
});
describe('util', () => {
describe('getUrl', () => {
it('should return from app', () => {
expect(getUrl()).toEqual('is from app');
});
it('should return from web', () => {
expect(getUrl()).toEqual('is from web');
});
});
});

How to test function defined on window object in Jest and typescript

I'm trying to test a call to a function defined on the global window object. I have looked at multiple examples but am still not able to run a simple test case.
Api.ts
import "./global.d";
const verifier = window.Verifier;
export class Api {
constructor(private readonly id: string) {}
public fetchData() {
return new Promise<object>((resolve, reject) => {
verifier.request({
request: {
id: this.id
},
onSuccess: (data: object) => {
resolve(data);
},
onFailure: () => {
reject("Error!");
}
});
});
}
}
Api.test.ts
import { Api } from "./Api";
let windowSpy: any;
describe("Test Apis", () => {
beforeEach(() => {
windowSpy = jest.spyOn(window, "window", "get");
});
afterEach(() => {
windowSpy.mockRestore();
});
it("should call the function", () => {
const mockedReplace = jest.fn();
windowSpy.mockImplementation(() => ({
Verifier: {
request: mockedReplace
}
}));
const api = new Api("123");
api.fetchData();
expect(mockedReplace).toHaveBeenCalled();
});
});
global.d.ts
import { Verifier } from "./verifier";
declare global {
interface Window {
Verifier: Verifier;
}
}
verifier.d.ts
type RequestPayload = {
request: {
id: string;
};
onSuccess: (data: object) => void;
onFailure: () => void;
};
type verifyCode = number;
export interface Verifier {
request: (requestPayload: RequestPayload) => verifyCode;
}
I have also created a codesandbox example for easy reference
https://codesandbox.io/s/cranky-mccarthy-2jj622?file=/src/verifier.d.ts
The problem here is your import order.
When you import { Api } from "./api";, you run const verifier = window.Verifier;, which, at the time, is undefined.
If you change the order of the imports and spies it should work as expected:
import { RequestPayload } from "./verifier";
const mockRequest = jest
.fn()
.mockImplementation((requestPayload: RequestPayload) => 1);
jest.spyOn(window, "window", "get").mockImplementation(() => {
return {
Verifier: {
request: mockRequest,
},
} as unknown as Window & typeof globalThis;
});
// // // // //
import { Api } from "./api";
// // // // //
describe("Test Apis", () => {
let api: Api;
beforeEach(() => {
jest.clearAllMocks();
});
beforeEach(() => {
api = new Api("123");
});
it("should have valid function", () => {
expect(typeof api.fetchData).toBe("function");
});
it("should call the function", () => {
api.fetchData();
expect(mockRequest).toHaveBeenCalled();
});
});
You could also think about using window.Verifier directly, making the tests a bit cleaner:
export class Api {
constructor(private readonly id: string) {}
public fetchData() {
return new Promise<object>((resolve, reject) => {
window.Verifier.request({
request: {
id: this.id,
},
onSuccess: (data: object) => {
resolve(data);
},
onFailure: () => {
reject("Error!");
},
});
});
}
}
describe("Test Apis", () => {
let api: Api, mockRequest: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockRequest = jest
.fn()
.mockImplementation((requestPayload: RequestPayload) => 1);
jest.spyOn(global, "window", "get").mockImplementation(() => {
return {
Verifier: {
request: mockRequest,
},
} as unknown as Window & typeof globalThis;
});
});
beforeEach(() => {
api = new Api("123");
});
it("should have valid function", () => {
expect(typeof api.fetchData).toBe("function");
});
it("should call the function", () => {
api.fetchData();
expect(mockRequest).toHaveBeenCalled();
});
});

How to mock DynamoDBDocumentClient constructor with Jest (AWS SDK V3)

I'm working with Jest to mock my AWS services, and more specifically DynamoDB and DynamoDBDocumentClient.
My code is currently similar to this :
import { DynamoDBClient } from "#aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient } from "#aws-sdk/lib-dynamodb"
const ddbClient = new DynamoDBClient({})
const ddbDocumentClient = DynamoDBDocumentClient.from(ddbClient, config)
and the test spec looks like this:
jest.mock("#aws-sdk/client-dynamodb", () => ({
DynamoDBClient: jest.fn(() => ({
put: (params) => mockAwsResponse("DynamoDBClient", "GetCommand", params),
put: (params) => mockAwsResponse("DynamoDBClient", "PutCommand", params),
})),
}))
jest.mock("#aws-sdk/lib-dynamodb", () => ({
DynamoDBDocumentClient: jest.fn(() => ({
get: (params) => console.log("In DynamoDBClient get", params),
put: (params) => mockAwsResponse("DynamoDBDocumentClient", "PutCommand", params),
from: (params) => mockAwsResponse("DynamoDBDocumentClient", "from", params),
})),
}))
Unfortunately Jest returns me this error : TypeError: lib_dynamodb_1.DynamoDBDocumentClient.from is not a function and I believe it's because I'm incorrectly mocking DynamoDBDocumentClient, as its constructor is a static method.
Thanks in advance for your help!
EDIT: Just noticed that it used the AWS SDK v3 for JavaScript, if that helps.
In your example, you import your modules and run call your constructor right away (before entering any method in your script), this means that the object you are trying to mock needs to be available before entering any test methods.
Try to place your mocks before (and outside of) your tests something like this:
import { DynamoDBClient } from "#aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient } from "#aws-sdk/lib-dynamodb"
/* whatever constants you need for your mocks */
jest.mock('#aws-sdk/client-dynamodb', () => {
return {
DynamoDBClient: jest.fn().mockImplementation(() => {
return {};
}),
};
});
jest.mock('#aws-sdk/lib-dynamodb', () => {
return {
DynamoDBDocumentClient: {
from: jest.fn().mockImplementation(() => {
return {
send: jest.fn().mockImplementation((command) => {
let res = 'something';
if(command.name == 'GetCommand'){
res = 'something else (a constant from earlier...)';
}
/* return whatever needs to be returned... */
return Promise.resolve(res);
}),
};
}),
},
/* Return your other docClient methods here too... */
GetCommand: jest.fn().mockImplementation(() => {
return { name: 'GetCommand' };
}),
};
});
// then you can implement your tests
describe('Endpoint something', () => {
it('Should pass or something...', async () => {
/* your test here... */
});
});
Now your functions should be available when they are needed.

How do I mock this method chain in Jest?

zoomOut(callback) {
// Zooms out the current screen
this.view.current.zoomOut(300).done(() => {
(hasCallback(callback)) && callback();
});
}
I'm trying to test the function above but I keep getting the following error:
TypeError: this.view.current.zoomOut(...).done is not a function
How can I mock this method chain in Jest?
Thanks to BudgieInWA, I was able to solve this problem by returning done.
For those who are testing a React component with Enzyme, here's how you can do it:
it('should call callback', () => {
const wrapper = shallow(<Zoom {...minProps}/>);
const instance = wrapper.instance();
const callback = jest.fn();
instance.view = {
current: {
zoomOut: jest.fn(() => {
return {
done: jest.fn((callback) => {
callback();
})
};
})
}
};
expect(callback).toHaveBeenCalledTimes(0);
instance.zoomOut(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
You could try this:
const mockZoomOut = jest.fn(() => ({ done(cb) { cb(); } }));
const mockThis = {
view: {
current: {
zoomOut: mockZoomOut,
},
},
};
test('it does', () => {
const cb = jest.fn();
zoomOut.apply(mockThis, [cb]);
expect(mockZoomOut).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledTimes(1);
});
See Jest Mock Functions and fn.apply.
If you are testing the behaviour of the class as a whole, then you could set up the instance that you are testing to have this.view.current.zoomOut be mockZoomOut somehow.

fetch-mock with jasmine not triggering then

I have this constant:
export const clientData = fetch(`${process.env.SERVER_HOST}clientData.json`)
.then(response => response.json());
Which works properly, and Now I'm working on the test of this, with Jasmine and fetch-mock
This is my test:
import { clientData } from '../../../src/js/services/client-data.fetch';
import fetchMock from 'fetch-mock';
describe('test', () => {
const exampleResponse = {
clientData: 'test'
};
beforeAll(() => {
fetchMock.mock('*', exampleResponse);
});
it('ooo', () => {
console.log('here', clientData);
var a = clientData;
a.then(b=> console.log(b))
});
});
The console.log of clientData returns a Promise (Which is fine), but the then is never triggered.
Not seeing why, what is wrong with my code?
This happens because the test execution is synchronous in nature and it doesn't wait for the assertion to happen, so you have to pass a done callback and call it from your test inside the then callback
Like this:
import { clientData } from '../../../src/js/services/client-data.fetch';
import fetchMock from 'fetch-mock';
describe('test', () => {
const exampleResponse = {
clientData: 'test'
};
beforeAll(() => {
fetchMock.mock('*', exampleResponse);
});
it('ooo', (done) => {
console.log('here', clientData);
var a = clientData;
a.then(b=> {
console.log(b);
done();
})
});
});

Categories

Resources