React unit test with jest in es6 - node.js

I am pretty new in react world and trying to write simple friendslist application. I wrote my friends store in es6 style and using babel as transpiler from es5 to es6.
import AppDispatcher from '../dispatcher/app_dispatcher';
import { EventEmitter } from 'events';
import FRIENDS_CONST from '../constants/friends';
const CHANGE_EVENT = 'CHANGE';
let friendsList = [];
let add = (name) => {
let counter = friendsList.length + 1;
let newFriend = {
id: counter,
name: name
};
friendsList.push(newFriend);
}
let remove = (id) => {
let index = friendsList.findIndex(e => e.id == id);
delete friendsList[index];
}
let FriendsStore = Object.assign({}, EventEmitter.prototype, {
getAll: () => {
return friendsList;
},
emitChange: () => {
this.emit(CHANGE_EVENT);
},
addChangeListener: (callback) => {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: (callback) => {
this.removeListener(CHANGE_EVENT, callback);
}
});
AppDispatcher.register((action) => {
switch (action.actionType) {
case FRIENDS_CONST.ADD_FRIENDS:
add(action.name);
FriendsStore.emitChange();
break;
case FRIENDS_CONST.REMOVE_FRIENDS:
remove(action.id);
FriendsStore.emitChange();
break;
}
});
export default FriendsStore;
Now I want to test my store and wrote the unit test also in es6
jest.dontMock('../../constants/friends');
jest.dontMock('../friends_store');
describe('FriendsStore', () => {
import FRIENDS from '../../constants/friends';
import AppDispatcher from '../../dispatcher/AppDispatcher';
import FriendsStore from '../friends_store';
let FakeAppDispatcher;
let FakeFriendsStore;
let callback;
let addFriends = {
actionType: FRIENDS.ADD_FRIENDS,
name: 'Many'
};
let removeFriend = {
actionType: FRIENDS.REMOVE_FRIENDS,
id: '3'
};
beforeEach(function() {
FakeAppDispatcher = AppDispatcher;
FakeFriendsStore = FriendsStore;
callback = AppDispatcher.register.mock.calls[0][0];
});
it('Should initialize with no friends items', function() {
var all = FriendsStore.getAll();
expect(all).toEqual([]);
});
});
When I execute the test with statement npm test, I've got the error message:
> react-starterify#0.0.9 test /Volumes/Developer/reactjs/app5
> echo "Error: no test specified"
Error: no test specified
What am I doing wrong? The file structure looks as follow:

I did it following the tutorial:
npm install --save-dev jest babel-jest babel-preset-es2015 babel-preset-react react-test-renderer
then
add to package.json
"scripts": {
"test": "jest"
},
"jest": {
"testPathDirs": [
"src/main/resources/web_pages/__tests__"
]
},
Result:
PASS src/main/resources/web_pages/__tests__/modules/utils/ValidationUtil.spec.js (5.214s)
✓ ValidateEmail (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 6.092s, estimated 7s
Ran all test suites.

To test ES6 syntax and JSX files, they need to be transformed for Jest. Jest has a config variable where you can define a preprocessor (scriptPreprocessor). You can use the babel-jest preprocessor:
Make the following changes to package.json:
{
"devDependencies": {
"babel-jest": "*",
"jest-cli": "*"
},
"scripts": {
"test": "jest"
},
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": ["es6", "js"],
"moduleFileExtensions": ["js", "json", "es6"]
}
}
And run:
$ npm install

Related

Can't mock a ES6 imported function in NodeJS

I've been trying for some time to mock the fetchLiveMatches imported function with no success. I've been browsing for some ideas but I think I ran out of it, so I could use some help. Any ideas of what I am doing wrong?
live.test.js
import * as liveController from "./live";
import { jest } from "#jest/globals";
import * as liveService from "../service/live";
import { buildReq, buildRes, buildNext } from "../utils/testingHelper";
jest.mock("../service/live");
beforeEach(() => {
jest.clearAllMocks();
});
describe("Live Controller", () => {
test("calls fetchLiveMatches function to fetch from external API", async () => {
const req = buildReq();
const res = buildRes();
const next = buildNext();
await liveController.getLiveMatches(req, res, next);
expect(next).not.toHaveBeenCalled();
expect(liveService.fetchLiveMatches).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.status).toHaveBeenCalledTimes(1);
});
});
service/live.js
import axios from "axios";
async function fetchLiveMatches() {
// Some hidden code
return axios({
method: "get",
url: `${API_FOOTBALL_BASE_URL}${GET_EVENTS}${MATCH_LIVE}${WIDGET_KEY}${TIMEZONE}${DETAILS}`,
headers: {}
}).then(res => res.data);
}
export { fetchLiveMatches };
jest.config.js
export default {
testEnvironment: "jest-environment-node",
transform: {}
};
package.json
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"axios": "^1.1.3",
"eslint": "^8.26.0",
"jest": "^29.2.2",
"prettier": "^2.7.1"
},
"scripts": {
"start": "node --watch index.js",
"start:no-watch": "node index.js",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
}
}
Test Error Output
Live Controller › calls fetchLiveMatches function to fetch from external API
expect(received).toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function fetchLiveMatches]
Just posting the solution I found for anyone who eventually is facing the same problem:
First, since I'm using ES6/module imports without Babel I changed the mock function to unstable_mockModule, and then based on the docs I decided to try dynamic imports in test scope after mocking the modules.
If you're using ES module imports then you'll normally be inclined to put your import statements at the top of the test file. But often you need to instruct Jest to use a mock before modules use it. For this reason, Jest will automatically hoist jest.mock calls to the top of the module (before any imports). To learn more about this and see it in action, see this repo.
The test component works with the following code:
import { jest } from "#jest/globals";
import { buildReq, buildRes, buildNext } from "../utils/testingHelper";
describe("Live Controller", () => {
test("calls fetchLiveMatches function to fetch from external API", async () => {
jest.unstable_mockModule("../service/live", () => ({
fetchLiveMatches: jest.fn(() => [])
}));
const { getLiveMatches } = await import("./live");
const { fetchLiveMatches } = await import("../service/live");
const req = buildReq();
const res = buildRes();
const next = buildNext(msg => console.log(msg));
await getLiveMatches(req, res, next);
expect(fetchLiveMatches).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(200);
expect(res.status).toHaveBeenCalledTimes(1);
});
});

No coverage information is generated for vscode extension using nyc

To generate the code coverage report for vscode extension, i am using nyc and running those via vscode test runner.
Source : https://code.visualstudio.com/api/working-with-extensions/testing-extension
Project structure:
out
-test
-unit
-testcases.js
-index.js
- runTest.js
``
"test": "rm -rf .nyc_output/ && nyc node ./out/test/runTest.js",
"nyc": {
"extends": "#istanbuljs/nyc-config-typescript",
"require": [
"ts-node/register",
"source-map-support/register"
],
"report-dir": ".",
"reporter": [
"text",
"html",
"lcov"
],
"exclude": ["out/test/**"],
"include": [ "out/**/*.js" ],
"check-coverage": true
},
index.ts file:
import * as path from 'path';
import * as Mocha from 'mocha';
import * as glob from 'glob';
export function run(): Promise<void> {
const mocha = new Mocha({
ui: 'tdd',
color: true,
timeout: 20000,});
const testsRoot = path.resolve(__dirname, '../unit');
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}
// Add files to the test suite
files.forEach(f => {
mocha.addFile(path.resolve(testsRoot, f));
});
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
e(err);
}
});
});
}
runTest.ts file:
import * as path from 'path';
import { runTests } from 'vscode-test';
async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
// The path to test runner
// Passed to --extensionTestsPath
//const extensionTestsPath = path.resolve(__dirname, './unit/index-coverage');
const extensionTestsPath = path.resolve(__dirname, './unit/index');
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
} catch (err) {
//console.error('Failed to run tests');
process.exit(1);
}
}
main();
I was not able to generate code coverage report.It generates report but without any information.
What i am doing wrong here??
There are couple of ways to do this. I found some valuable information while checking below link:
How do a generate vscode TypeScript extension coverage report
Seems the easiest one is from user frenya. but the other two also gives valuable information.

Create React App doesn't properly mock modules from __mocks__ directory

I have a working example with Jest and mocks from __mocks__ directory that works :
With simple Jest setup
// package.json
{
"name": "a",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "jest"
},
...
"devDependencies": {
"jest": "^26.6.3"
},
"dependencies": {
"#octokit/rest": "^18.0.12"
}
}
And then /index.js :
const { Octokit } = require("#octokit/rest");
const octokit = new Octokit();
module.exports.foo = function() {
return octokit.repos.listForOrg({ org: "octokit", type: "public" })
}
with its test (/index.test.js):
const { foo } = require("./index.js");
test("foo should be true", async () => {
expect(await foo()).toEqual([1,2]);
});
and the mock (/__mocks__/#octokit/rest/index.js):
module.exports.Octokit = jest.fn().mockImplementation( () => ({
repos: {
listForOrg: jest.fn().mockResolvedValue([1,2])
}
}) );
This works quite well and tests pass.
With Create React App
However doing the same with Create React App seems to be giving me a weird result:
// package.json
{
"name": "b",
"version": "0.1.0",
"dependencies": {
"#octokit/rest": "^18.0.12",
"#testing-library/jest-dom": "^5.11.4",
"#testing-library/react": "^11.1.0",
"#testing-library/user-event": "^12.1.10",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
...
}
And then /src/foo.js:
import { Octokit } from "#octokit/rest";
const octokit = new Octokit();
module.exports.foo = function() {
return octokit.repos.listForOrg({ org: "octokit", type: "public" })
}
with its test (/src/foo.test.js):
const { foo} = require("./foo.js");
test("foo should be true", async () => {
expect(await foo()).toEqual([1,2]);
});
and the very same mock (under /src/__mocks__/#octokit/rest/index.js):
export const Octokit = jest.fn().mockImplementation( () => ({
repos: {
listForOrg: jest.fn().mockResolvedValue([1,2])
}
}) );
This makes the test fail:
FAIL src/foo.test.js
✕ foo should be true (2 ms)
● foo should be true
expect(received).toEqual(expected) // deep equality
Expected: [1, 2]
Received: undefined
2 |
3 | test("foo should be true", async () => {
> 4 | expect(await foo()).toEqual([1,2]);
| ^
5 | });
6 |
7 |
at Object.<anonymous> (src/foo.test.js:4:25)
After reading a lot it seems that I can't make __mocks__ work inside Create React App. What's the problem?
The problem is that CRA's default Jest setup automatically resets the mocks, which removes the mockResolvedValue you set.
One way to solve this, which also gives you more control to have different values in different tests (e.g. to test error handling) and assert on what it was called with, is to expose the mock function from the module too:
export const mockListForOrg = jest.fn();
export const Octokit = jest.fn().mockImplementation(() => ({
repos: {
listForOrg: mockListForOrg,
},
}));
Then you configure the value you want in the test, after Jest would have reset it:
import { mockListForOrg } from "#octokit/rest";
import { foo } from "./foo";
test("foo should be true", async () => {
mockListForOrg.mockResolvedValueOnce([1, 2]);
expect(await foo()).toEqual([1, 2]);
});
Another option is to add the following into your package.json to override that configuration, per this issue:
{
...
"jest": {
"resetMocks": false
}
}
This could lead to issues with mock state (calls received) being retained between tests, though, so you'll need to make sure they're getting cleared and/or reset somewhere.
Note that you generally shouldn't mock what you don't own, though - if the interface to #octokit/rest changes your tests will continue to pass but your code won't work. To avoid this issue, I would recommend either or both of:
Moving the assertions to the transport layer, using e.g. MSW to check that the right request gets made; or
Writing a simple facade that wraps #octokit/rest, decoupling your code from the interface you don't own, and mocking that;
along with higher-level (end-to-end) tests to make sure everything works correctly with the real GitHub API.
In fact, deleting the mocks and writing such a test using MSW:
import { rest } from "msw";
import { setupServer } from "msw/node";
import { foo } from "./foo";
const server = setupServer(rest.get("https://api.github.com/orgs/octokit/repos", (req, res, ctx) => {
return res(ctx.status(200), ctx.json([1, 2]));
}));
beforeAll(() => server.listen());
afterAll(() => server.close());
test("foo should be true", async () => {
expect(await foo()).toEqual([1, 2]);
});
exposes that the current assumption about what octokit.repos.listForOrg would return is inaccurate, because this test fails:
● foo should be true
expect(received).toEqual(expected) // deep equality
Expected: [1, 2]
Received: {"data": [1, 2], "headers": {"content-type": "application/json", "x-powered-by": "msw"}, "status": 200, "url": "https://api.github.com/orgs/octokit/repos?type=public"}
13 |
14 | test("foo should be true", async () => {
> 15 | expect(await foo()).toEqual([1, 2]);
| ^
16 | });
17 |
at Object.<anonymous> (src/foo.test.js:15:25)
Your implementation should actually look something more like:
export async function foo() {
const { data } = await octokit.repos.listForOrg({ org: "octokit", type: "public" });
return data;
}
or:
export function foo() {
return octokit.repos.listForOrg({ org: "octokit", type: "public" }).then(({ data }) => data);
}

Problems initiating Web API File object in test with Babel and Jest

While trying to upgrade babel to v7 in an existing test setup with Jest and Enzyme, I have encountered a problem where Web API File is empty. Although it responds to methods like myFile.name.
Packages used:
babel => 7.6.4
jest => 24.9.0
babel-jest => 24.9.0
Not really an expert with babel, but this is my config
Babe config
const env = process.env.NODE_ENV;
const isProd = () => env === 'production';
const isDev = () => env === 'development';
const isTest = () => env === 'test';
const babelPresetEnvOptions = () => {
const options = {};
if (isTest()) {
options.targets = { node: 'current' };
} else {
// Disable polyfill transforms
options.useBuiltIns = false;
// Do not transform modules to CommonJS
options.modules = false;
}
if (isProd()) {
options.forceAllTransforms = true;
}
return options;
};
const presets = [
[require.resolve('#babel/preset-env'), babelPresetEnvOptions()],
[require.resolve('#babel/preset-react'), { development: isDev() }],
];
const plugins = [
[require.resolve('#babel/plugin-proposal-decorators'), { legacy: true }],
[require.resolve('#babel/plugin-proposal-class-properties'), { loose: true }],
require.resolve('#babel/plugin-proposal-object-rest-spread'),
require.resolve('#babel/plugin-transform-react-jsx'),
require.resolve('#babel/plugin-transform-runtime'),
];
if (isTest()) {
// Compiles import() to a deferred require()
plugins.push(require.resolve('babel-plugin-dynamic-import-node'));
} else {
// Adds syntax support for import()
plugins.push(require.resolve('#babel/plugin-syntax-dynamic-import'));
}
module.exports = api => {
api.assertVersion('^7.6');
return {
presets,
plugins,
};
};
Jest setup
"jest": {
"rootDir": "./webpack",
"setupFiles": [
"<rootDir>/test/jestsetup.js"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"moduleNameMapper": {
"^.+\\.(css|scss)$": "identity-obj-proxy"
},
"transform": {
"^.+\\.(js|jsx)?$": "babel-jest"
}
},
Problem:
When I try to initiate a file object
const lastModified = 1511256180536;
const myImageFile = new File([''], 'pixel.gif', { type: 'image/gif', lastModified });
console.log(myImageFile); // Also results in => File {}
console.log(imageFile.name); // return 'pixel.gif'
The test snapshot fails as shown below, which I can't explain why.
- file={
- File {
- Symbol(impl): FileImpl {
- "_buffer": Object {
- "data": Array [],
- "type": "Buffer",
- },
- "isClosed": false,
- "lastModified": 1511256180536,
- "name": "pixel.gif",
- "type": "image/gif",
- Symbol(wrapper): [Circular],
- },
- }
- }
+ file={File {}}
Even a hint on this would be great.
Babel debug
#babel/preset-env: `DEBUG` option
Using targets:
{
"node": "12.11"
}
Using modules transform: auto
Using plugins:
syntax-async-generators { "node":"12.11" }
syntax-object-rest-spread { "node":"12.11" }
syntax-json-strings { "node":"12.11" }
syntax-optional-catch-binding { "node":"12.11" }
transform-modules-commonjs { "node":"12.11" }
proposal-dynamic-import { "node":"12.11" }
Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.

Node.js Jasmine helpers onComplete

I want to set up a callback to run after jasmine has completed all tasks.
This is what I've tried:
package.json
{
"scripts": {
"test": "jasmine"
}
...
"jasmine": "^2.8.0"
}
spec/support/jasmine.json
{
"helpers": [
"helpers/env.js",
"helpers/**/*.js"
],
...
}
spec/helpers/env.js
jasmine.onComplete( () => console.log('yay, done') )
but keep on getting errors
$ npm test
...
jasmine.onComplete( () => console.log('yay, done') )
^
TypeError: jasmine.onComplete is not a function
...
It seems,
You are not going to initialize Jasmine.
var Jasmine = require('jasmine');
var jasmine = new Jasmine();
Refer this:
And then this should work:
jasmine.onComplete(function(passed) {
if(passed) {
console.log('All specs have passed');
}
else {
console.log('At least one spec has failed');
}
});

Resources