The function below is custom code we provide to a third party, hence the weird syntax.
const assignNameFunction = (exports.onExecutePostLogin = async (
event
) => {
const package = require("foo#2.23.0").Client;
package.doThing(event.bar)
});
I need to mock the package to unit test, one of the issues is the version. Due to the third party, I have to specify the version.
I tried the a few variants of:
import { thirdPartyFunction } from "./";
const mockFunc = jest.fn();
jest.mock("foo#2.23.0", ({
Client: {
doThing: mockFunc
}
}));
describe("Third Party Function", () => {
it("doThing is called", async () => {
const event = { bar: "foo" };
await thirdPartyFunction(event)
expect(mockFunc).toBeCalled();
});
});
or instead of mock using
import all from "foo";
jest.spyOn(all, "Client").mockReturnValue(...);
But jest comes back with Cannot find module 'foo#2.23.0'
How can I mock the require from within the function?
Related
Is it possible to test startServer() call once launcApp is called?
import { Server } from './Server/Server';
export class Launcher {
private server: Server;
public constructor() {
this.server = new Server();
}
public launchApp() {
this.server.startServer();
}
}
I tried to do
Server.prototype.startServer = jest.fn();
new Launcher.launchApp();
expect(Server.prototype.startServer).toBeCalled();
but it fails
Your approach worked for me. The only problem with your snippet was that you are not instantiating your Launcher properly. It needs to be:
Server.prototype.startServer = jest.fn();
new Launcher(). launchApp();
expect(Server.prototype.startServer).toBeCalled();
But if you are looking for other ways to mock methods in classes, there are many to achieve it, see: Jest docs: The 4 ways to create an ES6 class mock. One of the ways is described under: https://jestjs.io/docs/es6-class-mocks#mocking-non-default-class-exports
So you can use jest.mock() with your module path, then return an object with the key that is the same as the class export name.
const mockStartServer = jest.fn();
jest.mock("./Server/Server", () => {
return {
Server: jest.fn().mockImplementation(() => {
return { startServer: mockStartServer };
}),
};
});
Note that mockStartServer is defined and initialized with jest.fn() on the outer scope, and outside the returned object, and that's because Jest's mock behavior:
"Since calls to jest.mock() are hoisted to the top of the file, Jest
prevents access to out-of-scope variables. By default, you cannot
first define a variable and then use it in the factory. Jest will
disable this check for variables that start with the word mock.
However, it is still up to you to guarantee that they will be
initialized on time. Be aware of Temporal Dead Zone". src: jest documentation
Then the test would be:
import { Launcher } from "./luncher";
import { Server } from "./Server/Server";
const mockStartServer = jest.fn();
jest.mock("./Server/Server", () => {
return {
Server: jest.fn().mockImplementation(() => {
return { startServer: mockStartServer };
}),
};
});
it("should check for server start", () => {
const luncher = new Launcher();
luncher.launchApp();
expect(mockStartServer).toHaveBeenCalled();
});
You can also create a mock with spyOn, by overriding the implementation of the spied Server.prototype.startServer method.
const startServerMock = jest
.spyOn(Server.prototype, "startServer")
.mockImplementation(() => {});
it("should check for server start", () => {
const lun = new Launcher();
lun.launchApp();
expect(startServerMock).toHaveBeenCalled();
});
I'm attempting to mock a call to a class instance function on an ES6 module being imported by the code under test. I've followed the progress of the ES6 support and eventually stumbled onto this PR https://github.com/facebook/jest/pull/10976 which mentioned that in 27.1.1 support for jest.unstable_mockModule was added. I upgraded my version of Jest to take advantage and while the test doesn't error, it also doesn't seem to actually mock the module either.
This is the module under test:
// src/Main.mjs
import Responder from './Responder.mjs'
import Notifier from './Notifier.mjs'
export default {
async fetch(request, environment, context) {
let response
try {
response = new Responder(request, environment, context).respond()
} catch (error) {
return new Notifier().notify(error)
}
return response
}
}
Here is the test:
// test/Main.test.mjs
import { jest } from '#jest/globals'
import main from '../src/Main.mjs'
describe('fetch', () => {
test('Notifies on error', async () => {
const mockNotify = jest.fn();
jest.unstable_mockModule('../src/Notifier.mjs', () => ({
notify: mockNotify
}))
const notifierMock = await import('../src/Notifier.mjs');
await main.fetch(null, null, null)
expect(mockNotify).toHaveBeenCalled()
})
})
I'm trying to mock the call to Notify to expect it to have been called and while this will run, it raises an exception from inside the Notifier.notify() that is supposed to be mocked, so it appears that it isn't being mocked at all.
What am I missing? Any help is much appreciated. 🙏
I believe it's because you're importing main at the start of the file. You need to do a dynamic import the same way as Notifier.mjs
// test/Main.test.mjs
import { jest } from '#jest/globals'
describe('fetch', () => {
test('Notifies on error', async () => {
const mockNotify = jest.fn();
jest.unstable_mockModule('../src/Notifier.mjs', () => ({
notify: mockNotify
}))
const notifierMock = await import('../src/Notifier.mjs');
const main = await import('../src/Main.mjs');
await main.fetch(null, null, null)
expect(mockNotify).toHaveBeenCalled()
})
})
I'm trying to write a unit test for a Node.js project's logic using Jest.
However, most documentations only provide a case for importing a module or class, however, in my case, my module only contains functions.
So far I know that there are mainly three ways to test a function in Jest:
1) jest.fn()
2) jest.spyOn
3) jest.mock('path')
I have tried all three but none work.
I wanted to test if the function returns a correct value(string) when called.
I tried many different
Here's my code: (I will show short snippets of my code in the later parts)
getDefApCode.ts
export function getDefApCode(code: string) {
switch (code) {
case 'BKK':
return 'NRT'
case 'CTX':
return 'ICN'
case 'SIN':
return 'TPE'
default:
return code
}
}
export function getDefaultDepartureCode(code: string) {
return code ? getDefaultLocationCode(code) : 'LHR'
}
export function getDefaultDestinationCode(code: string) {
return code ? getDefaultLocationCode(code) : 'ZRH'
}
getDefAPCode.spec.ts >> Pattern 1 (using required + jest.fn)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefAPCode from "../../../src/controllers/logic/getDefAPCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
it("should get a default location code", async () => {
const getLocation = require('../../../src/controllers/logic/getDefAPCode');
const code = jest.fn(code => 'BKK');
const getCode = getLocation(code);
expect(getCode).toHaveBeenCalled();
});
});
Error Message:
TypeError: getLocation is not a function
getDefAPCode.spec.ts >> Pattern 2 (using spyON)
import { Connection, getConnection, getConnectionOptions } from "typeorm";
import { bootstrap, dbConnection } from "../../../src/app";
import { TourSearchParamsFactory } from "../../helpers/typeOrmFactory";
import * as getDefaultLocationCode from "../../../src/controllers/logic/getDefaultLocationCode";
describe("Logic Test", () => {
beforeAll(async () => {
await dbConnection(15, 3000);
});
afterAll(async () => {
const conn = getConnection();
await conn.close();
});
const { getDefaultLocationCode, getDefaultDepartureCode, getDefaultDestinationCode } = require('../../../src/controllers/logic/getDefaultLocationCode');
it("should get a default location code", async () => {
const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
getDefaultLocationCode.getDefaultLocationCode('AKJ');
expect(spy).toHaveBeenCalled();
});
});
These are some error messages appear when I tried a different pattern (I didn't keep track of all of the test code pattern, will add the test code pattern once I fixed docker)
Error Message:
Cannot spy the getDefaultLocationCode property because it is not a function; undefined given instead
31 | const spy = jest.spyOn(getDefaultLocationCode, 'getDefaultLocationCode');
Past Error Messages
error TS2349: This expression is not callable.
Type 'typeof import("/app/src/controllers/logic/getDefAPCode")' has no call signatures.
another one
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: string
Received has value: "NRT"
I figured out that I don't have to use mock function in this case.
I stored argument in a variable and then I use the variable instead using a string directly.
Here's how I edit my test code
it("should get a default location code", () => {
const code = 'BKK';
expect(code).toHaveBeenCalled();
});
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.
Im trying to mock property tz and a function using jest but i dont know to mock both things together:
If run something like:
jest.mock('moment-timezone', () => () => ({weekday: () => 5}))
jest.mock('moment-timezone', () => {
return {
tz: {
}
}
})
I can mock attribute tz or instruction moment(). How can i write a mock for cover this code?
const moment = require('moment-timezone')
module.exports.send = () => {
const now = moment()
moment.tz.setDefault('America/Sao_Paulo')
return now.weekday()
}
Thanks
You could take advantage of the second parameter of jest.mock(), which would let you specify a custom implementation of the mocked module to use in testing.
Inside this custom implementation, you can also define some convenience helpers to emulate expected implementation values (e.g. weekday()).
// send-module.test.js
jest.mock('moment-timezone', () => {
let weekday
const moment = jest.fn(() => {
return {
weekday: jest.fn(() => weekday),
}
})
moment.tz = {
setDefault: jest.fn(),
}
// Helper for tests to set expected weekday value
moment.__setWeekday = (value) => weekday = value
return moment;
})
const sendModule = require('./send-module')
test('test', () => {
require('moment-timezone').__setWeekday(3)
expect(sendModule.send()).toBe(3)
})
Do note that manually providing the mock per test file can get tedious and repetitive if the module being mocked has a huge API surface. To address the latter case, you can consider authoring some manual mocks to make them reusable (i.e. using the __mocks__ directory convention) and supplement that by using jest.genMockFromModule().
The Jest documentation has some guidance about this.