How to mock const method in jest? - javascript

I unit test code in typescript, use jest. Please teach me how to mock getData to return the expected value. My code as below:
// File util.ts
export const getData = async () => {
// Todo something
return data;
}
// File execution.ts import { getData } from './util';
function execute()
{
// todo something
const data = await getData();
// todo something
}

The problem is that your function returns a promise. Depends on how you use it there are several ways to mock it.
The simplest way would be to mock it directly, but then it will always return the same value:
// note, the path is relative to your test file
jest.mock('./util', () => ({ getData: () => 'someValue' }));
If you want to test both the resolved and the rejected case you need to mock getData so it will return a spy where you later on can change the implementation use mockImplementation. You also need to use async/await to make the test work, have a look at the docs about asynchronous testing:
import { getData } from './util';
jest.mock('./util', () => ({ getData: ()=> jest.fn() }));
it('success case', async () => {
const result = Promise.resolve('someValue');
getData.mockImplementation(() => result);
// call your function to test
await result; // you need to use await to make jest aware of the promise
});
it('error case', async () => {
const result = Promise.reject(new Error('someError'));
getData.mockImplementation(() => result);
// call your function to test
await expect(result).rejects.toThrow('someError');
});

Try the following in your test file.
Import the function from the module.
import { getData } from './util';
Then mock the module with the function and its return value after all the import statements
jest.mock('./util', () => ({ getData: jest.fn() }))
getData.mockReturnValue("abc");
Then use it in your tests.

Because mocking expression functions can be a real pain to get right, I'm posting a full example below.
Scenario
Let's say we want to test some code that performs some REST call, but we don't want the actual REST call to be made:
// doWithApi.ts
export const doSomethingWithRest = () => {
post("some-url", 123);
}
Where the post is a function expression in a separate file:
// apiHelpers.ts
export const post = (url: string, num: number) => {
throw Error("I'm a REST call that should not run during unit tests!");
}
Setup
Since the post function is used directly (and not passed in as a parameter), we must create a mock file that Jest can use during tests as a replacement for the real post function:
// __mocks__/apiHelpers.ts
export const post = jest.fn();
Spy and Test
Now, finally inside the actual test, we may do the following:
// mockAndSpyInternals.test.ts
import {doSomethingWithRest} from "./doWithApi";
afterEach(jest.clearAllMocks); // Resets the spy between tests
jest.mock("./apiHelpers"); // Replaces runtime functions inside 'apiHelpers' with those found inside __mocks__. Path is relative to current file. Note that we reference the file we want to replace, not the mock we replace it with.
test("When doSomethingWithRest is called, a REST call is performed.", () => {
// If we want to spy on the post method to perform assertions we must add the following lines.
// If no spy is wanted, these lines can be omitted.
const apiHelpers = require("./apiHelpers");
const postSpy = jest.spyOn(apiHelpers, "post");
// Alter the spy if desired (e.g by mocking a resolved promise)
// postSpy.mockImplementation(() => Promise.resolve({..some object}))
doSomethingWithRest();
expect(postSpy).toBeCalledTimes(1)
expect(postSpy).toHaveBeenCalledWith("some-url", 123);
});
Examples are made using Jest 24.9.0 and Typescript 3.7.4

Related

mock a service with Jest

Trying to write a test to provide code coverage for the following code :
note : there are other functions in the service but just listing one for brevity.
export const service = {
getById: async (id) => {
const url = `/api/customers/${id}/names`
const {data} = await axios.get(url, axiosOptions);
return data;
}
I'm attempting to simply provide code coverage with this test:
note : I have attempted to use require instead of import but that does not seem to work.
import {service} from './requests';
it("mocks the getById function", () => {
service.getById = jest.fn();
expect(service.getById.mock).toBeTruthy();
}
This test passes however seems to provide no code coverage.
I've attempted to mock out the axios call but I seem to get nowhere as examples I've found of implementations are not working for me currently.
Does anyone have ideas and an example how I could provide code coverage for the service please?
Update : to sonEtLumiere's answer
jest.mock('./service', () => ({
getById: jest.fn().mockResolvedValue({ data : "hello"}),
}));
describe('test', () => {
it('mocks the service", async () => {
service.getById.mockResolvedValue({data: "hello});
const data = await service.getById(1);
expect(data).toEqual({data:"hello"});
})
})
Currently getting back error :
Cannot read properties of undefined (reading 'getById')
Any thoughts on why I'm getting this error?
To mock a service using Jest, you can use the jest.mock() function to create a mocked version of the service. For example:
jest.mock('path/to/service', () => ({
getById: jest.fn().mockResolvedValue({ /* mocked data */ }),
}));
Then, in your test file, you can import the mocked version of the service and use the mock property on the function to control its behavior. For example, you can use .mockResolvedValue to set the resolved value of the function, or use .mockRejectedValue to make the function throw an error.
import { service } from 'path/to/service';
describe('test', () => {
it('mocks the service', async () => {
service.getById.mockResolvedValue({ /* mocked data */ });
const data = await service.getById(1);
expect(data).toEqual({ /* mocked data */ });
});
});
I do agree with #Lin Du's comment, if you want to test service.getById, you should be mocking what the method depends on, in this case axios.get.
But following along with your question, the issue is that the named export in ./requests is an object containing the getById property which is the method you want to test. So jest.mock should look like:
jest.mock("./requests.js", () => ({
service: {
getById: jest.fn(),
},
}))
Then your test will pass as you expected:
it("mocks the getById function", async () => {
service.getById.mockResolvedValueOnce({ data: "hello" })
const data = await service.getById(1)
expect(data).toEqual({ data: "hello" })
})
But again, if you want to test a method and have proper coverage, what you need to mock is the method's dependency, not the method itself, e.g:
import { service } from "./requests"
import axios from "axios"
jest.mock("axios")
test("service.getById", async () => {
axios.get.mockResolvedValueOnce({ data: "hello" })
const result = await service.getById(1)
expect(result).toBe("hello")
})

Trigger mocked callback argument with Jest

Using Jest, I'd like to trigger the configure callback argument. If I were to write this with a sinon stub, I could do something like configure.yields('my value'). Does Jest have anything similar? To help illustrate what I'm after, I created a simple example.
I've imported mymodule, instantiated, and called the configure function. I'd like my test to trigger this callback. i.e. (err, results) => ...
import MyModule from 'mymodule';
export function execute(key, value) {
return new Promise((resolve, reject) => {
new MyModule().configure(key, value, (err, result) => {
// I need my test to trigger this section of code...
return resolve('my resolved value')
})
})
}
This test is mocking MyModule and setting configure to a jest.fn(). While I have everything mocked, I'm not able to specify the configure args and trigger a specific argument. Ideally, I'd like to do something like mockFn.yields('my value') to trigger the configure callback.
import MyModule from 'mymodule';
jest.mock('mymodule');
describe('Test Example', () => {
test('should trigger mocked args callback', async () => {
const mockFn = jest.fn();
const key = 'my_key';
const value = 'my_value';
const actual = require('./src/my-service');
MyModule.mockImplementation(() => {
return {
configure: mockFn
};
});
await actual.execute(key, value)
// how can I trigger the mocked configure argument callback?
});
});
Test currently fails because it cannot trigger the callback function. Error states: Async callback was not invoked
The error means that a promise that test async function returns wasn't settled. This happens because execute returns a pending promise, mocked configure doesn't call a callback that is supposed to resolve it.
configure should be mocked correctly and behave the same way as original implementation regarding callback argument:
mockFn.mockImplementation((key, value, cb) => cb(null, 'some result'));
const promise = actual.execute(key, value);
expect(mockFn).toBeCalledWith(key, value, expect.any(Function));
await expect(promise).resolves.toBe('my resolved value');

Jest testing context / spy on mocked variables created outside of functions (class level) Postmark

I'm trying to do some testing in Jest but getting stuck with a mock/spy. I've managed to get the test working but only by changing my implementation (which I feel dirty about).
Here's the test:
import * as postmark from 'postmark';
jest.mock('postmark');
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers
};
});
postmark.AccountClient = AccountClient;
import accountApi from './account-api';
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
expect(mockGetServers).toHaveBeenCalledTimes(1);
});
});
});
Here's the working implementation:
import * as postmark from 'postmark';
const accountToken = 'some-token-number';
const listServers = async () => {
try {
const accountClient = postmark.AccountClient(accountToken);
const servers = await accountClient.getServers();
return servers;
} catch (e) {
console.log('ERROR', e);
}
};
export default {
listServers
}
Here's the original implementation:
import * as postmark from 'postmark';
const accountToken = 'some-token-number';
const accountClient = postmark.AccountClient(accountToken);
const listServers = async () => {
try {
const servers = await accountClient.getServers();
return servers;
} catch (e) {
console.log('ERROR', e);
}
};
export default {
listServers
}
The only change is where in the code the accountClient is created (either inside or outside of the listServers function). The original implementation would complete and jest would report the mock hadn't been called.
I'm stumped as to why this doesn't work to start with and guessing it's something to do with context of the mock. Am I missing something about the way jest works under the hood? As the implementation of accountApi will have more functions all using the same client it makes sense to create one for all functions rather than per function. Creating it per function doesn't sit right with me.
What is different about the way I have created the accountClient that means the mock can be spied on in the test? Is there a way I can mock (and spy on) the object that is created at class level not at function level?
Thanks
Am I missing something about the way jest works under the hood?
Two things to note:
ES6 import calls are hoisted to the top of the current scope
babel-jest hoists calls to jest.mock to the top of their code block (above everything including any ES6 import calls in the block)
What is different about the way I have created the accountClient that means the mock can be spied on in the test?
In both cases this runs first:
jest.mock('postmark');
...which will auto-mock the postmark module.
Then this runs:
import accountApi from './account-api';
In the original implementation this line runs:
const accountClient = postmark.AccountClient(accountToken);
...which captures the result of calling postmark.AccountClient and saves it in accountClient. The auto-mock of postmark will have stubbed AccountClient with a mock function that returns undefined, so accountClient will be set to undefined.
In both cases the test code now starts running which sets up the mock for postmark.AccountClient.
Then during the test this line runs:
await accountApi.listServers();
In the original implementation that call ends up running this:
const servers = await accountClient.getServers();
...which drops to the catch since accountClient is undefined, the error is logged, and the test continues until it fails on this line:
expect(mockGetServers).toHaveBeenCalledTimes(1);
...since mockGetServers was never called.
On the other hand, in the working implementation this runs:
const accountClient = postmark.AccountClient(accountToken);
const servers = await accountClient.getServers();
...and since postmark is mocked by this point it uses the mock and the test passes.
Is there a way I can mock (and spy on) the object that is created at class level not at function level?
Yes.
Because the original implementation captures the result of calling postmark.AccountClient as soon as it is imported, you just have to make sure your mock is set up before you import the original implementation.
One of the easiest ways to do that is to set up your mock with a module factory during the call to jest.mock since it gets hoisted and runs first.
Here is an updated test that works with the original implementation:
import * as postmark from 'postmark';
jest.mock('postmark', () => { // use a module factory
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers // NOTE: this returns the same mockGetServers every time
};
});
return {
AccountClient
}
});
import accountApi from './account-api';
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
const mockGetServers = postmark.AccountClient().getServers; // get mockGetServers
expect(mockGetServers).toHaveBeenCalledTimes(1); // Success!
});
});
});
I think you might want to look at proxyquire.
import * as postmark from 'postmark';
import * as proxyquire from 'proxyquire';
jest.mock('postmark');
const mockGetServers = jest.fn();
const AccountClient = jest.fn(() => {
return {
getServers: mockGetServers
};
});
postmark.AccountClient = AccountClient;
import accountApi from proxyquire('./account-api', postmark);
describe('account-api', () => {
describe('listServers', () => {
it('calls postmark listServers', async () => {
await accountApi.listServers();
expect(mockGetServers).toHaveBeenCalledTimes(1);
});
});
});
Note that I have not tested this implementation; tweaking may be required.

How to mock dynamical for mongoDB method?

In my application code there are several places, where I have to connect to a DB and get some data.
For my unit tests (I'm using JestJS), I need to mock this out.
Let's assume this simple async function:
/getData.js
import DB from './lib/db'
export async function getData () {
const db = DB.getDB()
const Content = db.get('content')
const doc = await Content.findOne({ _id: id })
return doc
}
The DB connection is in a separate file:
/lib/db.js
import monk from 'monk'
var state = {
db: null
}
exports.connect = (options, done) => {
if (state.db) return done()
state.db = monk(
'mongodb://localhost:27017/db',
options
)
return state.db
}
exports.getDB = () => {
return state.db
}
You can see, I'll recieve the DB and get a collection. After this I will recieve the data.
My attempt for the mock so far:
/tests/getData.test.js
import { getData } from '../getData'
import DB from './lib/db'
describe('getData()', () => {
beforeEach(() => {
DB.getDB = jest.fn()
.mockImplementation(
() => ({
get: jest.fn(
() => ({
findOne: jest.fn(() => null)
})
)
})
)
})
test('should return null', () => {
const result = getData()
expect(result).toBeNull()
})
})
Maybe this is not the best way to do it...? I'm very happy for every improvement.
My question is where to put the DB mock as there are multiple tests and every test needs a different mock result for the findOne() call.
Maybe it is possible to create a function, which gets called with the needed parameter or something like that.
First I just want to note that testing this proof-of-concept function as-is appears low in value. There isn't really any of your code in there; it's all calls to the DB client. The test is basically verifying that, if you mock the DB client to return null, it returns null. So you're really just testing your mock.
However, it would be useful if your function transformed the data somehow before returning it. (Although in that case I would put the transform in its own function with its own tests, leaving us back where we started.)
So I'll suggest a solution that does what you asked, and then one that will hopefully improve your code.
Solution that doesn't require changing getData() - Not Recommended:
You can create a function that returns a mock that provides a findOne() that returns whatever you specify:
// ./db-test-utils
function makeMockGetDbWithFindOneThatReturns(returnValue) {
const findOne = jest.fn(() => Promise.resolve(returnValue));
return jest.fn(() => ({
get: () => ({ findOne })
}));
}
Then in your code file, call DB.getDB.mockImplementation in beforeEach or beforeAll above each test, passing in the desired return value, like this:
import DB from './db';
jest.mock('./db');
describe('testing getThingById()', () => {
beforeAll(() => {
DB.getDB.mockImplementation(makeMockGetDbWithFindOneThatReturns(null));
});
test('should return null', async () => {
const result = await getData();
expect(result).toBeNull();
});
});
Solution that makes testing much easier across your DB-related code
This question is really exciting, because it is a wonderful illustration of the value of having each function do only one thing!
getData appears to be very small - only 3 lines plus a return statement. So at first glance it doesn't seem to be doing too much.
However, this tiny function has very tight coupling with the internal structure of DB. It has dependency on:
DB - a singleton
DB.getDB()
DB.getDB().get()
DB.getDB().get().findOne()
This has some negative repercussions:
If DB ever changes its structure, which since it uses a 3rd party component, is possible, then every function you have that has these dependencies will break.
It's very hard to test!
The code isn't reusable. So every function that accesses the DB will need to call getDB() and db.get('collection'), resulting in repeated code.
Here's one way you could improve things, while making your test mocks much simpler.
Export db instead of DB
I could be wrong, but my guess is, every time you use DB, the first thing you'll do is call getDB(). But you only ever need to make that call once in your entire codebase. Instead of repeating that code everywhere, you can export db from ./lib/db.js instead of DB:
// ./lib/db.js
const DB = existingCode(); // However you're creating DB now
const dbInstance = DB.getDB();
export default dbInstance;
Alternatively, you could create the db instance in a startup function and then pass it in to a DataAccessLayer class, which would house all of your DB access calls. Again only calling getDB() once. That way you avoid the singleton, which makes testing easier because it allows dependency injection.
Add a helper function to get the DB collection
// ./lib/db.js
const DB = existingCode(); // However you're creating DB now
const dbInstance = DB.getDB();
export function getCollectionByName(collectionName){
return dbInstance.get(collectionName);
}
export default dbInstance;
This function is so trivial it might seem unnecessary. After all, it has the same number of lines as the code it replaces! But it removes the dependency on the structure of dbInstance (previously db) from calling code, while documenting what get() does (which is not obvious from its name).
Now your getData, which I'm renaming getDocById to reflect what it actually does, can look like this:
import { getCollectionByName } from './lib/db';
export async function getDocById(id) {
const collection = getCollectionByName('things');
const doc = await collection.findOne({ _id: id })
return doc;
}
Now you can mock getCollectionByName separately from DB:
// getData.test.js
import { getDocById } from '../getData'
import { getCollectionByName } from './lib/db'
jest.mock('./lib/db');
describe('testing getThingById()', () => {
beforeEach(() => {
getCollectionByName.mockImplementation(() => ({
findOne: jest.fn(() => Promise.resolve(null))
}));
});
test('should return null', async () => {
const result = await getDocById();
expect(result).toBeNull();
});
});
This is just one approach and it could be taken much further. For example we could export findOneDocById(collectionName, id) and/or findOneDoc(collectionName, searchObject) to make both our mock and calls to findOne() simpler.

React and jest mock module

I am creating an application in which I use redux and node-fetch for remote data fetching.
I want to test the fact that I am well calling the fetch function with a good parameter.
This way, I am using jest.mock and jasmine.createSpy methods :
it('should have called the fetch method with URL constant', () => {
const spy = jasmine.createSpy('nodeFetch');
spy.and.callFake(() => new Promise(resolve => resolve('null')));
const mock = jest.mock('node-fetch', spy);
const slug = 'slug';
actionHandler[FETCH_REMOTE](slug);
expect(spy).toHaveBeenCalledWith(Constants.URL + slug);
});
Here's the function that I m trying to test :
[FETCH_REMOTE]: slug => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
await fetch(Constants.URL + slug);
addLocal();
};
}
AS you can see, I am trying to log the console.log(fetch()) behavior, and I am having the default promise to resolve given by node-fetch, and not the that I've mock with Jest and spied with jasmine.
Do you have an idea what it doesn't work ?
EDIT : My test displayed me an error like my spy has never been called
Your action-handler is actually a action handler factory. In actionHandler[FETCH_REMOTE], you are creating a new function. The returned function taskes dispatch as a parameter and invokes the code you are showing.
This means that your test code will never call any function on the spy, as the created function is never invoked.
I think you will need to create a mock dispatch function and do something like this:
let dispatchMock = jest.fn(); // create a mock function
actionHandler[FETCH_REMOTE](slug)(dispatchMock);
EDIT:
To me, your actionHandler looks more like an actionCreator, as it is usually called in redux terms, though I personally prefer to call them actionFactories because that is what they are: Factories that create actions.
As you are using thunks(?) your actionCreater (which is misleadingly named actionHandler) does not directly create an action but another function which is invoked as soon as the action is dispatched. For comparison, a regular actionCreator looks like this:
updateFilter: (filter) => ({type: actionNames.UPDATE_FILTER, payload: {filter: filter}}),
A actionHandler on the other hand reacts to actions being dispatched and evaluates their payload.
Here is what I would do in your case:
Create a new object called actionFactories like this:
const actionFactories = {
fetchRemote(slug): (slug) => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
let response = await fetch(Constants.URL + slug);
var responseAction;
if (/* determine success of response */) {
responseAction = actionFactories.fetchSuccessful(response);
} else {
responseAction = actionFactories.fetchFailed();
}
dispatch(responseAction);
};
}
fetchFailed(): () => ({type: FETCH_FAILED, }),
fetchSuccessful(response): () => ({type: FETCH_FAILED, payload: response })
};
Create an actionHandler for FETCH_FAILED and FETCH_SUCCESSFUL to update the store based on the response.
BTW: Your console.log statement does not make much sense too me, since fetch just returns a promise.

Categories