I am trying to write test scripts using mocha js for my react code. The test helper file is like this
browser.js
var baseDOM = '<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>';
var jsdom = require('jsdom').jsdom;
global.document = jsdom(baseDOM);
global.window = document.defaultView;
if ( global.self != null) {
console.log(' global.self >>>>> ' + global.self);
} else {
global.self = global.this;
}
global.navigator = {
userAgent: 'node.js'
};
The test script is like this (component.specs.js)
'use strict';
import ReactDOM from 'react-dom';
import React from 'react';
import { expect } from 'chai';
import { mount, shallow, render } from 'enzyme';
import { App } from '../../js/app.js';
import sinon from 'sinon';
var dispatch = function() {
`enter code here` console.log('>>>>>>>> Mocking dispatch ');
};
describe('App', function () {
it('App calls check', () => {
sinon.spy(App.prototype, 'check');
const enzymeWrapper = mount(<App {...props} />);
expect(App.prototype.check.calledOnce).to.equal(true);
});
So, when I run npm test, I get the error as 'jsdom is not a function'. What is wrong with the code?
I'm not familiar with jsdom but based on the examples I don't think you're using it correctly. jsdom's example code:
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
What this means for you is that var jsdom = require('jsdom').jsdom; should be var jsdom = require('jsdom').JSDOM;. The example also uses new.
That said, you may want to check out jest for testing node/react - it's very easy to setup and works well.
Related
I am trying to write some unit tests and I am getting errors in the test and I am trying to understand why the errors happen.
The unit test is for the index.ts file that calls the features/index.ts file. I am stubbing the default export from features/index.ts with sinon. But when I run the tests I get the following error TypeError: Cannot read property 'resolve' of undefined pointing at the file features/feature1.ts
I have added the relavant extracts from the tests and typescript files below.
features/feature1.ts
import path from "path";
import fs from "fs";
import {Setup} from "../types";
const TEMPLATE_ROOT = path.resolve(__dirname,"../../templates");
const INDEX_TEMPLATE = fs.readFileSync(TEMPLATE_ROOT, "index.js"), "utf8");
export const setup: Setup = async ({config, options}) => {
// Internal code removed
}
features/index.ts
import {setup as feature1} from "./feature1.ts";
import {setup as feature2} from "./feature2.ts";
type FeatureTypes = "feature1" | "feature2"
type Features = {
[key in FeatureTypes]: Setup;
};
const features: Features = {
feature1: feature1,
feature2: feature2
}
export default features
index.ts
import features from "./features"
import { Config, Options } from "./types";
export async function init(config: Config, options: Options): Promise<void> {
const nextFeature = options.features ? options.features.shift() : undefined;
if (nextFeature) {
// Other irrelevant code
await Promise.resolve(features[nextFeature]({ config, options }));
return init(config, options);
}
}
index.spec.ts
import { expect } from "chai";
import * as sinon from "sinon";
import { init } from '.'
import * as features from "./features";
import { Config, Options } from "./types"
describe("init", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();
let featuresStub: sinon.SinonStub;
beforeEach(() => {
featuresStub = sandbox.stub(features, "default").returns({
feature1: sandbox.stub().resolves(),
feature2: sandbox.stub().resolves(),
});
});
afterEach(() => {
sandbox.restore();
});
it("should call setup features", async () => {
const setup: Setup = {
features: [
"feature1",
"feature2",
],
};
await init({}, options);
expect(featuresStub).to.have.been.calledOnce;
});
// rest of tests
});
I have also tried the changing the stub setup to be:
import * as feature1 from ".features/feature1";
import * as feature2 from ".features/feature2";
// Other code
describe("init", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();
let feature1Stub: sinon.SinonStub;
let feature2Stub: sinon.SinonStub;
beforeEach(() => {
feature1Stub = sandbox.stub(feature1, "setup");
feature2Stub = sandbox.stub(feature2, "setup");
feature1Stub.resolves()
feature2Stub.resolves()
});
// Rest of code and tests
});
I don't know why it would be trying to run code const TEMPLATE_ROOT = path.resolve(__dirname,"../../templates"); if I have stubbed the function that calls it.
Figured it out the imports were wrong
import path from "path";
import fs from "fs";
should be:
import * as path from "path";
import * as fs from "fs";
So I've been trying to get this to work, but it keeps telling me that socket.io is undefined. I looked at some similar problems people have had with webpack and React, and have currently tried putting socket.io and all of node_modules in the externals part of webpack.config.js. At this point, I'm not quite sure what's going on. If it helps, I'm running a flask backend with flask_socketio running too.
webpack.config.js:
const appPackageJson = require(paths.appPackageJson);
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const webpackDevClientEntry = require.resolve(
'react-dev-utils/webpackHotDevClient'
);
const reactRefreshOverlayEntry = require.resolve(
'react-dev-utils/refreshOverlayInterop'
);
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);
// Get the path to the uncompiled service worker (if it exists).
const swSrc = paths.swSrc;
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
return false;
}
try {
require.resolve('react/jsx-runtime');
return true;
} catch (e) {
return false;
}
})();
var nodeModules = {};
fs.readdirSync('node_modules')
.filter(function(x) {
return ['.bin'].indexOf(x) === -1;
})
.forEach(function(mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function (webpackEnv) {
externals: nodeModules;
//lots of webpack stuff
};
socketio initialization:
import ReactDOM from 'react-dom';
import reportWebVitals from './reportWebVitals';
import './component_styling/main.scss'
import axios from 'axios'
import AppRouter from './Components/Router'
import {io} from 'socket.io-client'
export const axios_instance = axios.create({
baseURL: 'http://127.0.0.1:5000'
});
const socket = io.connect("http://127.0.0.1:5000");
axios_instance.interceptors.request.use(
function(config) {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = 'Bearer ' + token;
}
return config;
},
function(error) {
return Promise.reject(error);
}
);
ReactDOM.render(
<React.StrictMode>
<AppRouter />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
export default socket;
If anyone was wondering, I fixed this.
Switched import {io} from 'socket.io-client'
to import io from 'socket.io-client'
I am running my test cases by npm run test -- --watch where test is mocha --require babel-polyfill --require babel-core/register --require ./test/withDom.js --require ./test/test_helper.js --recursive ./test command to run test cases using mocha.
My components uses Jquery actively to manipulate DOM and handle the events.
In my first run all the test cases runs fine. But as soon as I modify some test cases and the watcher re-runs the test cases automatically; the jquery is not able to find the dom elements using selectors hereafter. This is evident because page DOM is not modified when I console log the wrapper.html() or body.innerHTML.
I will be very grateful if any of you resolve this.
Here is the code
withDom.js
import { JSDOM } from 'jsdom';
const jsdom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>');
const { window } = jsdom;
function copyProps(src, target) {
Object.defineProperties(target, {
...Object.getOwnPropertyDescriptors(src),
...Object.getOwnPropertyDescriptors(target),
});
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
global.requestAnimationFrame = function (callback) {
return setTimeout(callback, 0);
};
global.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
copyProps(window, global);
test_helper.js
import _$ from 'jquery';
import { mount, render, shallow, configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { expect } from 'chai';
import React from 'react';
import ReactDOM from 'react-dom';
import ReactTestUtils from 'react-dom/test-utils';
import { MemoryRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducers from '../src/reducers';
configure({ adapter: new Adapter() });
global.expect = expect;
global.mount = mount;
global.render = render;
global.shallow = shallow;
const $ = _$(window);
function renderComponent(ComponentClass, props = {}, state = {}) {
const componentInstance = ReactTestUtils.renderIntoDocument(
<Provider store = {createStore(reducers, state)}>
<MemoryRouter>
<ComponentClass {...props} />
</MemoryRouter>
</Provider>
);
console.log('helper',componentInstance);
return $(ReactDOM.findDOMNode(componentInstance));
}
export {renderComponent};
example_test.js
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { MemoryRouter, Link } from 'react-router-dom';
import thunk from 'redux-thunk';
import ItemList from '../../src/components/ItemList';
import RandomRater from '../../src/containers/RandomRater';
import { loadBooks } from '../../src/actions/index';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
let store, books, wrapper, params;
describe('<RandomRater/> Component',() => {
beforeEach(()=>{
store = mockStore();
store.dispatch(loadBooks());
books = store.getActions()[0].payload;
params = { 'params': {'title' : 'Html & CSS Design & Build website' } };
});
it('renders <RandomRater/> containing <ItemList/> and <Link/>',()=>{
wrapper = mount(
<MemoryRouter>
<RandomRater store={store} books={books} match={params} />
</MemoryRouter>,
{ attachTo: document.getElementById('root') }
);
expect(wrapper).to.exist;
expect(wrapper.contains(ItemList)).to.equal(true);
expect(wrapper.contains(Link)).to.equal(true);
expect(wrapper.text()).to.contains('Start Random Rating');
expect(wrapper.text()).to.contains('Back');
});
it('<RandomRater/> random rates the books',()=>{
//Below line is not modifying the dom after the first run.
wrapper.find('#btn-random-rate').simulate('click',{button:0});
console.log('body',document.body.innerHTML);
//This functions only runs on first run and not after that.
setTimeout(()=>{
expect(wrapper.text()).to.contains('Stop Random Rating')
},200);
wrapper.detach();
});
after(()=>{
wrapper.detach();
});
});
Note:
If I run the given mocha command manually each time the issue does not occur. It only occurs when I run with --watch.
I have referred following SO post but the my issue remain unresolved.
Update:
Here is the github repo with complete code. You can modify the test cases for RandomRater (see given RandomRater test file here) and Rater to reproduce the problem. (See DOM after the simulating click event at the Start Rating button for RandomRater and similarly for stars in Rater)
I'm creating a unit test on some code in React. But there is some action that didn't seem to work.
Let say I have a testing.js containing at the begenning:
const images = require.context('../../../media/promotions', true);
This is the unit test i'm trying ( testing.test.js ) :
import React from 'react';
import { shallow } from 'enzyme';
import { Testing } from '../../src/components/Testing';
let wrapper, instance;
beforeAll(() => {
wrapper = shallow(
<Testing />
);
instance = wrapper.instance();
});
describe('Testing', () => {
it('renders without crashing', () => {
expect(wrapper).toMatchSnapshot();
});
})
But the application crushes and this is the error that I get :
TypeError: require.context is not a function
The solution is to change the library:
import requireContext from 'require-context.macro';
And then require them this way:
const images = requireContext('../../../media/promotions', true);
A component is importing a library that includes a native module. Here is a contrived example:
import React from 'react';
import { View } from 'react-native';
import { Answers } from 'react-native-fabric';
export default function MyTouchComponent({ params }) {
return <View onPress={() => { Answers.logContentView() }} />
}
And here is the relevant part of Answers from react-native-fabric:
var { NativeModules, Platform } = require('react-native');
var SMXAnswers = NativeModules.SMXAnswers;
When importing this component in a mocha test, this fails on account that SMXAnswers is undefined:
How do you mock SMXAnswers or react-native-fabric so that it doesn't break and allows you to test your components?
p.s.: you can see the full setup and the component I'm trying to test on GitHub.
Use mockery to mock any native modules like so:
import mockery from 'mockery';
mockery.enable();
mockery.warnOnUnregistered(false);
mockery.registerMock('react-native-fabric', {
Crashlytics: {
crash: () => {},
},
});
Here is a complete setup example:
import 'core-js/fn/object/values';
import 'react-native-mock/mock';
import mockery from 'mockery';
import fs from 'fs';
import path from 'path';
import register from 'babel-core/register';
mockery.enable();
mockery.warnOnUnregistered(false);
mockery.registerMock('react-native-fabric', {
Crashlytics: {
crash: () => {},
},
});
const modulesToCompile = [
'react-native',
].map((moduleName) => new RegExp(`/node_modules/${moduleName}`));
const rcPath = path.join(__dirname, '..', '.babelrc');
const source = fs.readFileSync(rcPath).toString();
const config = JSON.parse(source);
config.ignore = function(filename) {
if (!(/\/node_modules\//).test(filename)) {
return false;
} else {
const matches = modulesToCompile.filter((regex) => regex.test(filename));
const shouldIgnore = matches.length === 0;
return shouldIgnore;
}
}
register(config);