jest winston format.errors is not a function - jestjs

I have an app that uses winston for logging. I am trying to setup a jest test for a function, that calls another function, etc., and the test for the function eventually fails at an attempt to use winston exported functions:
services\__tests__>jest search.test.js
FAIL ./search.test.js
● Test suite failed to run
TypeError: format.errors is not a function
26 | const logger = createLogger({
27 | level: config.logging.level,
> 28 | format: format.combine(format.errors({ stack: true }), errorStackFormat(), config.logging.logFormat()),
| ^
29 | transports: [
30 | new transports.Console({
31 | stderrLevels: ['error', 'critical'],
at Object.<anonymous> (helpers/logger.js:28:33)
at Object.<anonymous> (db/connection/connection.js:2:16)
/search.test.js
jest.mock('winston', () => ({
format: jest.fn(() => ({
errors: jest.fn(),
combine: jest.fn(),
apply: jest.fn(),
})),
createLogger: jest.fn().mockImplementation(() => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
})),
transports: {
Console: jest.fn(),
},
}));
const search = require('../search');
process.env.NODE_ENV = 'development';
describe('services/search.js', () => {
it('searches ', async () => {
const query = {
mpn: '5555555555',
};
expect(
await search(query).then((result) => {
console.log('result', result);
return result;
})
).toEqual(['000']);
});
});
helpers/logger.js
const { createLogger, format, transports } = require('winston');
const config = require('../config');
require('express-async-errors');
const errorStackFormat = format((info) => {
if (info.level === 'error') {
if (!/^{.*?}$/.test(info.message)) return info;
const { code, reason, status, message } = JSON.parse(info.message);
return {
...info,
code,
reason,
status,
message,
};
}
return info;
});
const logger = createLogger({
level: config.logging.level,
format: format.combine(format.errors({ stack: true }), errorStackFormat(), config.logging.logFormat()),
transports: [
new transports.Console({
stderrLevels: ['error', 'critical'],
}),
],
exceptionHandlers: [new transports.Console(config.logging.format)],
exitOnError: true,
});
module.exports = logger;
If I try to only have format as an object, it fails the test on const errorStackFormat = format((info) because it's looking for format to be a function, so the mock needs to have format as a function, and also have properties (like errors) that are functions as well.
How can I get format.errors to work in the mock? I'm not doing something right, please help me figure out what I am doing wrong :) Thanks!

Well I figured it out, at least it's not complaining anymore about the original error. I needed to use my logger function that uses winston, like so:
jest.mock('../../helpers/logger', () => jest.fn());
const logger = require('../../helpers/logger');
logger.mockImplementation(() => () => ({
format: jest.fn(() => ({
errors: jest.fn(),
combine: jest.fn(),
apply: jest.fn(),
})),
createLogger: jest.fn().mockImplementation(() => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
})),
transports: {
Console: jest.fn(),
},
}));

Related

How to mock the winston-daily-rotate-file npm in JEST unit testing

I have mocked the node module "winston" as below in "mocks" folder. is there a way to mock the "winston-daily-rotate-file" within it?
const mFormat = {
printf: jest.fn(),
timestamp: jest.fn(),
simple: jest.fn(),
colorize: jest.fn(),
combine: jest.fn(),
splat: jest.fn()
};
const mTransports = {
Console: jest.fn(),
File: jest.fn(),
DailyRotateFile: jest.fn()
};
const logger = {
format: mFormat,
transports: mTransports,
createLogger: jest.fn().mockImplementation(function (creationOpts) {
return {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn()
};
})
};
module.exports = logger;

Jest mocks only work at top of file, never inside "describe" or "it" blocks

I'm trying to introduce a small change to an existing project without unit tests and decided I'd try to learn enough about nodejs and jest to include tests with my change. However, I cannot get mocks to work like I'd expect in, say, python. The project uses the "kubernetes-client" library from godaddy and tries to create a config object from the envvar "KUBECONFIG", like this:
// a few lines into server.js
// Instantiate Kubernetes client
const Client = require('kubernetes-client').Client
const config = require('kubernetes-client').config;
if (process.env.KUBECONFIG) {
client = new Client({
config: config.fromKubeconfig(config.loadKubeconfig(process.env.KUBECONFIG)),
version: '1.13'
});
}
else {
client = new Client({ config: config.getInCluster(), version: '1.9' });
}
In my testing environment, I don't want any API calls, so I'm trying to mock it out:
// __tests__/server.test.js
// Set up mocks at the top because of how server.js sets up k8s client
const k8sClient = require('kubernetes-client');
const narp = 'narp';
jest.mock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
throw narp;
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
const app = require('../server.js')
const supertest = require('supertest');
const requestWithSuperTest = supertest(app.app);
describe('Testing server.js', () => {
afterAll(() => {
app.server.close();
});
describe('Tests with k8s client throwing error when fetching configmaps', () => {
it("finds a resource's ingressGroup by name", () => {
var resource = {
"spec": {
"ingressClass": "foo",
"ingressTargetDNSName": "foo"
}
};
var ingressGroups = [
{
"ingressClass": "bar",
"hostName": "bar",
"name": "barName"
},
{
"ingressClass": "foo",
"hostName": "foo",
"name": "fooName"
}
];
expect(app.findMatchingIngressGroupForResource(resource, ingressGroups)).toBe("fooName");
});
it('GET /healthcheck should respond "Healthy"', async () => {
const resp = await requestWithSuperTest.get('/healthcheck');
console.log("Response in Testing Endpoints: " + JSON.stringify(resp));
expect(resp.status).toEqual(200);
expect(resp.type).toEqual(expect.stringContaining('text'));
expect(resp.text).toEqual('Healthy');
});
it('Tests getIngressGroups() rejects with error when it cannot get configmaps', async () => {
app.getIngressGroups()
.then()
.catch(error => {
expect(error).toEqual("Failed to fetch Ingress Groups: " + narp);
});
});
});
});
With this setup, the tests pass (although I suspect it's meaningless). If I try to move the mocks inside the describe or it block using a beforeEach function (or not) so that I can change the behavior to return mock data instead of throwing an error, I immediately get errors with the k8s client complaining it can't find my kubeconfig/clusterconfig:
$ npm run testj
> testj
> jest --detectOpenHandles
kubernetes-client deprecated require('kubernetes-client').config, use require('kubernetes-client/backends/request').config. server.js:45:44
kubernetes-client deprecated loadKubeconfig see https://github.com/godaddy/kubernetes-client/blob/master/merging-with-kubernetes.md#request-kubeconfig- server.js:49:42
FAIL __tests__/server.test.js
● Test suite failed to run
ENOENT: no such file or directory, open 'NOT_A_FILE'
44 | if (process.env.KUBECONFIG) {
45 | client = new Client({
> 46 | config: config.fromKubeconfig(config.loadKubeconfig(process.env.KUBECONFIG)),
| ^
47 | version: '1.13'
48 | });
49 | }
at node_modules/kubernetes-client/backends/request/config.js:335:37
at Array.map (<anonymous>)
at Object.loadKubeconfig (node_modules/kubernetes-client/backends/request/config.js:334:28)
at Object.eval [as loadKubeconfig] (eval at wrapfunction (node_modules/kubernetes-client/node_modules/depd/index.js:425:22), <anonymous>:5:11)
at Object.<anonymous> (server.js:46:46)
If anybody has run into this kind of behavior before or sees some obviously-wrong lines, I'd really appreciate any tips or information. Thanks!
I had to change a few things to get this working:
jest.doMock() instead of jest.mock()
use of let app inside the describe block instead of const app at module-scope
a beforeEach() which calls jest.resetModules()
an afterEach() which calls app.close()
in the it block which overrides the mock(s), explicitly call jest.resetModules() before overriding
in the it block which overrides the mock(s), call app.close() and re-initialize app before invoking the actual function-under-test/expect
Resulting test file:
// Set up mocks at the top because of how server.js sets up k8s client
const k8sClient = require('kubernetes-client');
const supertest = require('supertest');
const narp = 'narp';
describe('Testing server.js', () => {
let app;
let requestWithSuperTest;
beforeEach(() => {
jest.resetModules();
jest.doMock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
throw narp;
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
app = require('../server.js');
requestWithSuperTest = supertest(app.app);
});
afterEach(() => {
app.server.close();
});
it("finds a Resource's ingressGroup by name", () => {
var resource = {
"spec": {
"ingressClass": "foo",
"ingressTargetDNSName": "foo"
}
};
var ingressGroups = [
{
"ingressClass": "bar",
"hostName": "bar",
"name": "barName"
},
{
"ingressClass": "foo",
"hostName": "foo",
"name": "fooName"
}
];
expect(app.findMatchingIngressGroupForResource(resource, ingressGroups)).toBe("fooName");
});
it('GET /healthcheck should respond "Healthy"', async () => {
const resp = await requestWithSuperTest.get('/healthcheck');
console.log("Response in Testing Endpoints: " + JSON.stringify(resp));
expect(resp.status).toEqual(200);
expect(resp.type).toEqual(expect.stringContaining('text'));
expect(resp.text).toEqual('Healthy');
});
it('Tests getIngressGroups() rejects with error when it cannot get configmaps', async () => {
expect.assertions(1);
await app.getIngressGroups()
.catch(error => {
expect(error).toEqual("Failed to fetch Ingress Groups: " + narp);
});
});
it('Tests getIngressGroups() succeeds when it gets configmaps', async () => {
expect.assertions(1);
jest.resetModules();
jest.doMock('kubernetes-client', () => {
const noConfigmaps = jest.fn(() => {
console.log('Attempted to get mocked configmaps');
return Promise.resolve({
body: {
items: []
}
});
});
const namespaces = jest.fn().mockReturnValue({
configmaps: jest.fn().mockReturnValue({
get: noConfigmaps
})
});
const addCustomResourceDefinition = jest.fn().mockReturnThis()
const mockClient = {
api: {
v1: {
namespaces
}
},
addCustomResourceDefinition: jest.fn().mockReturnThis(),
};
return {
Client: jest.fn(() => mockClient),
config: {
fromKubeconfig: jest.fn().mockReturnThis(),
loadKubeconfig: jest.fn().mockReturnThis(),
getInCluster: jest.fn().mockReturnThis()
},
};
});
app.server.close();
app = require('../server.js');
await app.getIngressGroups()
.then(result => {
expect(result).toEqual([])
});
});
});

How to put jest.mock in a util file and import from each unit test?

I am using TypeScript with Jest to do unit tests. I need to mock 3rd party library like below code:
jest.mock('aws-sdk', () => {
return {
SQS: jest.fn(() => ({
deleteMessage: jest.fn().mockReturnValue({ promise: jest.fn() }),
})),
S3: jest.fn(() => ({
getObject: jest.fn().mockReturnValue({ promise: jest.fn() }),
})),
};
});
It works fine if I put this mock on the top of each test file. However, it doesn't work if I put above code in a separate file and import it from each unit test file.
For example, I create a testUtils.ts file:
export const mockUnitTest = () =>
jest.mock('aws-sdk', () => {
return {
SQS: jest.fn(() => ({
deleteMessage: jest.fn().mockReturnValue({ promise: jest.fn() }),
})),
S3: jest.fn(() => ({
getObject: jest.fn().mockReturnValue({ promise: jest.fn() }),
})),
};
});
then in each unit test file, it imports it:
import { mockUnitTest } from './testUtils';
mockUnitTest()
Does anyone know why above code doesn't work? I don't want to put this mock in every unit test file.

Jest.fn().mockReturnValue returns undefined

Below mentioned mocked function when used in a file called useFirestore gives the following error: Cannot read property 'collection' of undefined.
firebase.js
import Firebase from 'firebase/app';
import 'firebase/storage';
import 'firebase/firestore';
import 'firebase/auth';
const config = {'// Firebase config'};
const firebase = !Firebase.apps.length ? Firebase.initializeApp(config) : Firebase.app;
export const { serverTimestamp } = Firebase.firestore.FieldValue;
export default firebase;
setupTests.js
import '#testing-library/jest-dom';
jest.mock('./firebase', () => ({
storage: jest.fn(),
firestore: jest.fn().mockReturnedValue({
collection: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
onSnapshot: jest.fn().mockReturnThis(),
}),
}));
Custom Hook
import firebase from '../firebase';
function useFirestore(collectionName) {
const [docs, setDocs] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
const unsub = firebase
.firestore() // logs cannot read property collection of undefined
.collection(collectionName)
.orderBy('createdAt', 'desc')
.onSnapshot(
(snapshot) => {
setDocs(snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })));
setLoading(false);
},
(err) => {
console.log(err);
}
);
return unsub;
}, [collectionName]);
return {
docs,
loading,
};
}
export default useFirestore;
jest.config.js
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,jsx}',
'!<rootDir>/node_modules/',
'!<rootDir>/coverage/',
'!<rootDir>/build/',
],
moduleNameMapper: { '\\.(css|scss)$': '<rootDir>/src/utils/__mocks__/stylemock.js' },
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
};
As mentioned the above configuration returns undefined. However, when I change the mock implemenation of firestore to the following, it works correctly.
jest.mock('./firebase', () => ({
storage: jest.fn(),
firestore: () => ({ // not using jest.fn() and mockReturnValue
collection: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
onSnapshot: jest.fn().mockReturnThis(),
}),
}));
Please guide if anything's wrong here. Thanks
Simply a typo my friend! No such thing as mockReturnedValue on type jest.Mock, you probably want to use mockReturnValue.

Jest: how to restore to a previous mocked implementation (not the original)

I have a set of 3 tests, the first one has the base mocked implementation:
Team.query = jest.fn(() => ({
findOne: () => {
return {
is_disabled: false,
};
},
}));
In the second test, I perform a Team.query.mockImplementationOnce with the above, but I change is_disabled to true.
In the third test, I want to restore it back to the jest.fn implementation above. Is this possible?
You're good, no extra work required.
Since Team.query is the mock function, it will automatically revert to the previously mocked implementation after one call when it has been overridden with mockImplementationOnce:
const Team = { };
Team.query = jest.fn(() => ({
findOne: () => {
return {
is_disabled: false,
};
},
}));
test('Team.query', () => {
expect(Team.query().findOne().is_disabled).toBe(false); // Success!
Team.query.mockImplementationOnce(() => ({ findOne: () => ({ is_disabled: true }) }));
expect(Team.query().findOne().is_disabled).toBe(true); // Success!
expect(Team.query().findOne().is_disabled).toBe(false); // Success!
Team.query.mockImplementationOnce(() => ({ findOne: () => ({ is_disabled: 'some text' }) }));
expect(Team.query().findOne().is_disabled).toBe('some text'); // Success!
expect(Team.query().findOne().is_disabled).toBe(false); // Success!
});

Resources