Jest automatic-mocks: funny example outcome - node.js

this refers to the facebook example tutorials
// utils.js
// Copyright 2004-present Facebook. All Rights Reserved.
export default {
authorize: () => 'token',
isAuthorized: (secret) => secret === 'wizard',
};
below is the test file. Instead of adding auto mock at the config file, I added inside the code to show the differences.
import utils from './utils';
jest.enableAutomock();
test('implementation created by automock', () => {
expect(utils.authorize('wizzard')).toBeUndefined();
expect(utils.isAuthorized()).toBeUndefined();
});
outcome:
TypeError: Cannot read property 'default' of undefined
6 |
7 | test('implementation created by automock', () => {
> 8 | expect(utils.authorize('wizzard')).toBeUndefined();
| ^
9 | expect(utils.isAuthorized()).toBeUndefined();
10 | });
11 |
at Object.utils (__tests__/example/automatic-mocks/genMockFromModule.test.js:8:10)
Why is that? it happens to another file automock.test.js. The error message is the same.
// Copyright 2004-present Facebook. All Rights Reserved.
import utils from './utils';
jest.enableAutomock();
test('if utils are mocked', () => {
expect(utils.authorize.mock).toBeTruthy();
expect(utils.isAuthorized.mock).toBeTruthy();
});
test('mocked implementation', () => {
utils.authorize.mockReturnValue('mocked_token');
utils.isAuthorized.mockReturnValue(true);
expect(utils.authorize()).toBe('mocked_token');
expect(utils.isAuthorized('not_wizard')).toBeTruthy();
});

Below example works for me, I use jestjs with typescript and ts-jest.
the docs say:
Note: this method was previously called autoMockOn. When using babel-jest, calls to enableAutomock will automatically be hoisted to the top of the code block. Use autoMockOn if you want to explicitly avoid this behavior.
utils.ts:
const utils = {
getJSON: data => JSON.stringify(data),
authorize: () => 'token',
isAuthorized: secret => secret === 'wizard'
};
export default utils;
utils.spec.ts:
jest.enableAutomock();
import utils from './utils';
describe('automatic mocks test suites', () => {
it('should mock all methods of utils', () => {
expect((utils.getJSON as jest.Mock).mock).toBeTruthy();
expect(jest.isMockFunction(utils.authorize)).toBeTruthy();
expect(jest.isMockFunction(utils.isAuthorized)).toBeTruthy();
});
test('implementation created by automock', () => {
expect(utils.authorize()).toBeUndefined();
expect(utils.isAuthorized('wizard')).toBeUndefined();
});
it('mocked implementation', () => {
(utils.getJSON as jest.Mock).mockReturnValue(123);
expect(utils.getJSON({ name: 'test' })).toBe(123);
});
});
Unit test result:
PASS src/automatic-mocks/utils.spec.ts (17.906s)
automatic mocks test suites
✓ should mock all methods of utils (4ms)
✓ implementation created by automock (2ms)
✓ mocked implementation (1ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 22.923s, estimated 23s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/automatic-mocks

Related

Mongoose: looks like you're trying to test a Mongoose app with Jest's default jsdom test environment

Can anyone help here. I couldn't run my test file. Below is the error & test file.
jest.useFakeTimers();
import { orderController } from '../controllers/orderController';
import { orderService } from '../service/orderService';
const res: any = {
send(object: any) {
return object;
}
};
describe("Methods in orderController", () => {
test("checking an API", async () => {
const patientDetailsMock = await jest.spyOn(orderService, 'getPatientDetails');
//const req = {}
//await orderController.createOrder(req, res);
expect(patientDetailsMock).toHaveBeenCalled();
//console.log("hello..inside test",patientDetailsMock)
//expect(patientDetailsMock).toBeTruthy();
});
});
>chandanasriharimummaneni#PTRL671:~/Desktop/demo/JestTesting/node-orders$ npm test
> node-orders#0.0.1 test /home/chandanasriharimummaneni/Desktop/demo/JestTesting/node-orders
> jest
FAIL src/test/orderController.test.ts
Methods in orderController
✕ checking an API (3ms)
● Methods in orderController › checking an API
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
21 | //const req = {}
22 | //await orderController.createOrder(req, res);
> 23 | expect(patientDetailsMock).toHaveBeenCalled();
| ^
24 | //console.log("hello..inside test",patientDetailsMock)
25 | //expect(patientDetailsMock).toBeTruthy();
26 |
at Object.<anonymous> (src/test/orderController.test.ts:23:36)
console.warn node_modules/mongoose/lib/helpers/printJestWarning.js:4
Mongoose: looks like you're trying to test a Mongoose app with Jest's default jsdom test environment. Please make sure you read Mongoose's docs on configuring Jest to test Node.js apps: http://mongoosejs.com/docs/jest.html
console.info node_modules/common-component/lpl/utils/logger/logger.js:184
{ uniqId: '', req: '', jsonObject: '', description: '', arguments: '' } [
'AWS-MIS-Config',
'{"provider":"amazon","keyId":"AKIAT5D3HEZTLAOGKVPG","key":"ZrPLIGmGXWh/nPh0euj+042m+yUUJUzUYvwPMoRR","region":"us-east-1"}'
]
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 1.994s, estimated 2s
Ran all test suites.
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
What are ways to mock db and methods using mongoose ORM?
Is there any way to mock db connection with object reference ? Also help me to to clear the issue. I have changed jsdom test environment.

why can't you mock a re-exported primitive value?

I'm trying to change the value of a primitive config object during tests. One of my files under test re-exports a primitive that is conditional on the config values.
I'm finding that when the value is wrapped in a function, then mocking it and asserting on it works perfectly.
However when the value is re-exported as a primitive, the value is not mocked, and is undefined.
Simplified example:
config.ts
export const config = {
environment: 'test'
};
app.ts
import { config } from './config';
export const app = () => config.environment;
export const environment = config.environment;
app.spec.ts
import { app, environment } from './app';
import * as config from './config';
jest.mock('./config', () => ({
config: {},
}));
beforeEach(() => {
jest.resetAllMocks();
});
const mockConfig = config.config as jest.Mocked<typeof config.config>;
test('app', () => {
mockConfig.environment = 'prod';
expect(app()).toEqual('prod');
});
test('environment', () => {
mockConfig.environment = 'nonprod';
expect(environment).toEqual('nonprod');
});
The first test passes, but the second test "environment" fails. Why?
✕ environment (3 ms)
● environment
expect(received).toEqual(expected) // deep equality
Expected: "nonprod"
Received: undefined
19 | test('environment', () => {
20 | mockConfig.environment = 'nonprod';
> 21 | expect(environment).toEqual('nonprod');
| ^
22 | });
23 |
at Object.<anonymous> (config/app.spec.ts:21:29)
The problem could be related with the order files are read. The app file is the first one to be read, and then the config file is read because its imported on the app one. But, probably the app code run first, so the variable was set as undefined (because the config one had not a value at the time).
The same does not happen with the app function, because it reads the config variable only after the function is called. And at that time, the variable already was set.

ignore setup mock for one file

I have a decorator that I use on a lot of my methods. To not have to mock it each time, I have added a mock on jest.setup.js:
jest.mock('src/something', () => {
someMethod: jest.fn.mockImplementation(/*some implementation*/)
})
This works fine, but now I want to unit test this one method (the someMethod in this example) and I can't, since it brings up the mock. How can I ignore this mock for only this file/test?
You can use jest.unmock(moduleName) in the test file of a module. So that your module under test will use the real module rather than the mocked version.
Let's see an example:
./src/stackoverflow/73129547/something.ts:
export const someMethod = () => 1;
This is the module we want to mock.
./jest.setup.js:
jest.setTimeout(5 * 1000);
jest.mock('./stackoverflow/73129547/something', () => ({
someMethod: jest.fn().mockReturnValue(0),
}));
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['./jest.setup.js'],
};
We set up the mock using jest.mock() in the jest.setup.js file. So that every module under test will use the mocked version of this module.
Suppose our project has two modules: a.ts and b.ts, they use the someMethod exported from the something module.
./src/stackoverflow/73129547/a.ts:
import { someMethod } from './something';
export const a = someMethod;
./src/stackoverflow/73129547/b.ts:
import { someMethod } from './something';
export const b = someMethod;
The test suites of a and b modules:
./src/stackoverflow/73129547/a.test.ts:
import { a } from './a';
describe('73129547 - a', () => {
test('should pass', () => {
expect(a()).toBe(0);
});
});
./src/stackoverflow/73129547/b.test.ts:
import { b } from "./b";
describe('73129547 - b', () => {
test('should pass', () => {
expect(b()).toBe(0);
});
});
Test result:
PASS stackoverflow/73129547/b.test.ts
PASS stackoverflow/73129547/a.test.ts (9.829 s)
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 10.681 s
As you can see the expected execution result. Both the a and b modules use the mocked someMethod which has a mock return value: 0.
Now, we want to test the someMethod of the something module, we should test the real someMethod rather than the mocked one. Test mock implementations make no sense.
./src/stackoverflow/73129547/something.test.ts:
import { someMethod } from './something';
jest.unmock('./something');
describe('73129547 - something', () => {
test('should pass', () => {
expect(someMethod()).toBe(1);
});
});
Test result:
PASS stackoverflow/73129547/a.test.ts
PASS stackoverflow/73129547/b.test.ts
PASS stackoverflow/73129547/something.test.ts
Test Suites: 3 passed, 3 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.955 s, estimated 14 s
As you can see, the return value of real someMethod is 1. jest.unmock(moduleName) will unmock the module and return the real module.

How to run OS-agnostic Jest test files that check paths?

Let's say I have the following:
expect(func).toHaveBeenCalledWith('/path/to/file');
This would work fine on NIX operating systems. However, this test would fail on Windows platform because I should instead have
expect(func).toHaveBeenCalledWith('\path\to\file');
What's the best way to write tests so they are OS agnostic? I was looking at this article but that is basically saying write different tests for different OSes.
In general, you can extend expect to add the matching behaviour you want, there are lots of examples in jest-extended. For this case, perhaps using the tools available in path to test against the appropriate path for whatever OS the tests are running on:
import { matcherHint, printExpected, printReceived } from "jest-matcher-utils";
import * as path from "path";
expect.extend({
toMatchPath: (actual, expected) => {
const normalised = path.join(...expected.split("/"));
return actual === normalised
? { pass: true, message: passMessage(actual, normalised) }
: { pass: false, message: failMessage(actual, normalised) };
},
});
const passMessage = (actual, expected) => () => `${matcherHint(".not.toMatchPath")}
Expected value not to match:
${printExpected(expected)}
Received:
${printReceived(actual)}`;
const failMessage = (actual, expected) => () => `${matcherHint(".toMatchPath")}
Expected value to match:
${printExpected(expected)}
Received:
${printReceived(actual)}`;
In your tests you then always write POSIX-style paths /path/to/thing, and path takes care of providing the appropriate path separator for the current OS. In use:
describe("path matching", () => {
const actual = path.join("path", "to", "thing");
it("normalises paths for matching", () => {
expect(actual).toMatchPath("path/to/thing");
});
it("can be negated", () => {
expect(actual).not.toMatchPath("path/to/other/thing");
});
it("can be used asymmetrically", () => {
const fn = jest.fn();
fn(actual);
expect(fn).toHaveBeenCalledWith(expect.toMatchPath("path/to/thing"));
});
it("fails usefully", () => {
const fn = jest.fn();
fn(actual);
expect(fn).toHaveBeenCalledWith(expect.not.toMatchPath("path/to/thing"));
});
});
Output:
path matching
✓ normalises paths for matching (3 ms)
✓ can be negated
✓ can be used asymmetrically (2 ms)
✕ fails usefully (3 ms)
● path matching › fails usefully
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: not.toMatchPath<path/to/thing>
Received: "path/to/thing"
Number of calls: 1
44 | const fn = jest.fn();
45 | fn(actual);
> 46 | expect(fn).toHaveBeenCalledWith(expect.not.toMatchPath("path/to/thing"));
| ^
47 | });
48 | });
at Object.toHaveBeenCalledWith (server/demo.test.js:46:14)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 3 passed, 4 total
Snapshots: 0 total

Jest - getting error when mocking FS modules and calling config module

I'm writing unit tests with Jest trying to test a module which uses FS.
The module file:
import fs from 'fs';
import logger from './logger.utils';
export const getNumberOfFiles = async (targetDir: string): Promise<number> => {
// get number of folders
logger.info(`getNumberOfFiles from ${targetDir}/${fileName}`);
const numberOfFiles = await fs.readdirSync(targetDir);
return numberOfFiles.length;
};
Test file
import fs from 'fs';
import { getNumberOfFiles } from '../../src/utils/fs.utils';
jest.mock('fs');
describe('fs.utils', () => {
describe('getNumberOfFiles', () => {
it('Should return number', async () => {
fs.readdirSync = jest.fn();
const readdirSyncMock = fs.readdirSync = jest.fn();
readdirSyncMock.mockResolvedValue([1, 2, 3]);
const result = await getNumberOfFiles('targetDir');
expect(result).toEqual(3);
expect(readdirSyncMock.mock.calls.length).toEqual(1);
});
});
});
When I run the test file, I get the following error:
Config file ..../config/runtime.json cannot be read. Error code is: undefined. Error message is: Cannot read property 'replace' of undefined
1 | const cheggLogger = require('#chegg/logger');
2 | import loggingContext from './loggingContext';
> 3 | import config from 'config';
| ^
4 | import os from 'os';
5 | import constants from '../../config/constants';
6 |
at Config.Object.<anonymous>.util.parseFile (node_modules/config/lib/config.js:789:13)
at Config.Object.<anonymous>.util.loadFileConfigs (node_modules/config/lib/config.js:666:26)
at new Config (node_modules/config/lib/config.js:116:27)
at Object.<anonymous> (node_modules/config/lib/config.js:1459:31)
at Object.<anonymous> (src/utils/logger.utils.ts:3:1)
Content of logger.utils.ts
const internalLogger = require('internalLogger');
import loggingContext from './loggingContext';
import config from 'config';
import os from 'os';
import constants from '../../config/constants';
const logger = internalLogger.createLogger({
level: config.get(constants.LOG_LEVEL)
});
export default logger;
I assume that config is using FS, and once I mock the module, it fails.
How can I resolve this? Please advise
I'm guessing the problem comes from config also using the fs api but you are now mock entire module fs which makes all methods should be mocked before using.
But I have an idea for you by using jest.doMock which you can provide a factory for each test and just mock only method we need. Here is a draft idea:
describe('fs.utils', () => {
describe('getNumberOfFiles', () => {
it('Should return number', async () => {
jest.doMock('fs', () => ({
// Keep other methods still working so `config` or others can use
// so make sure we don't break anything
...jest.requireActual('fs'),
readdirSync: jest.fn(pathUrl => {
// Mock for our test path since `config` also uses this method :(
return pathUrl === 'targetDir' ? Promise.resolve([1, 2, 3]) : jest.requireActual('fs').readdirSync(pathUrl)
})
}));
// One of the thing we should change is to switch `require` here
// to make sure the mock is happened before we actually require the code
// we can also use `import` here but requires us do a bit more thing
// so I keep thing simple by using `require`
const {getNumberOfFiles} = require('../../src/utils/fs.utils');
const result = await getNumberOfFiles('targetDir');
expect(result).toEqual(3);
// you might stop assert this as well
// expect(readdirSyncMock.mock.calls.length).toEqual(1);
});
});
});
Just also want to check, if you created a config file as described here: https://www.npmjs.com/package/config#quick-start

Resources