Debbuging with Mocha and Chai ReactJS - node.js

I am trying to start writing some tests in an small app that I wrote from a React Tutorial
My Stack
node -> v8.7.0
mocha -> "^5.1.0"
jsdom -> "^11.8.0"
jquery -> "^3.3.1"
react-dom -> "^16.3.2"
react -> "^16.3.2"
chai -> "^4.1.2"
babel-register -> "^6.26.0"
Here is my test_helper
import { JSDOM } from 'jsdom';
import jquery from 'jquery';
import ReactTestUtils from 'react-dom/test-utils';
import React from 'react';
import ReactDOM from 'react-dom';
import { expect } from 'chai';
//Set up testing environment to run like a browser in the command line
const dom = new JSDOM('<!doctype html><html><body></body></html>');
global.window = dom.window;
global.document = dom.window.document;
const $ = jquery(global.window);
// Build a 'renderComponent' helper that should render a given react class
function renderComponent(ComponentClass) {
const componentInstance = ReactTestUtils.renderIntoDocument(<ComponentClass />);
return $(ReactDOM.findDOMNode(componentInstance));
}
//Build a helper for simulating events
//Set up chai-jquery
export { renderComponent, expect };
My board_test
import { renderComponent, expect } from '../test_helper';
import Board from '../../src/components/board';
describe('Board', () => {
it('exist', () => {
const component = renderComponent(Board);
expect(component).to.exist;
});
});
and in package.json I have
...
"scripts": {
...
"test": "mocha --require babel-register --require ./test/test_helper.js --recursive ./test",
"test:watch": "npm run test -- --watch"
}
...
When I run npm test I get this error, I understand that is related with renderComponent(...)
1) Board
exist:
TypeError: Cannot read property '0' of undefined
If someone want to download the project here is the link (the problem I am facing is under the branch addTests)
My Repo -> branch addTests
to change to the branch addTests
git checkout -b addTests origin/addTests

In your trunk
Delete your app_test in your test/component folder => (https://github.com/agusgambina/react-tic-tac-toe/tree/master/test/components).
Only if you are not using it, it seems to be a default boilerplate file
In your branch
You are testing board componenet without passing params.
in your code ( not testing ) this params are passing by game component.
I suggest you to modify your helper => (test\test_helper.js) to this
function renderComponent(ComponentClass,props) {
if (!props) props = {};
const componentInstance = ReactTestUtils.renderIntoDocument(<ComponentClass {...props}/>);
return $(ReactDOM.findDOMNode(componentInstance));
}
and your board test => (test\components\board_test.js) to:
const component = renderComponent(Board, YOUR_PROPS_HERE);
expect(component).to.exist;
now you can pass props to your renderComponent

Related

NextJS useRouter() returns null in unit test with Storybook add-on

I'm running into an issue when integrating my stories into my unit tests using Jest & React Testing Library in a NextJS app. In Storybook, my NextJS components render without issue with the Storybook Addon Next Router working as expected. However, in my Jest test file, my test throws an error because useRouter() returns null. I believe I've setup all the addons correctly.
My setup:
NextJS 12
Jest & React Testing Library
Storybook
Storybook Addon Next Router (for using Next router inside Storybook)
#storybook/testing-react (for integrating my stories, args & params with testing library)
The problem:
I've setup all the files according to the docs. The story renders fine inside Storybook and the useRouter() works thanks to the Storybook Next addon. However, the composeStories() function from #storybook/testing-react seems to fail to properly initialize the Next router Provider from the first addon. My unit test fails with the following error:
Test suite failed to run
TypeError: Cannot read property 'locale' of null
And points to the following line inside my component:
// Events.tsx
const { locale = 'en' } = useRouter();
This is my test file:
// Events.test.tsx
import React from 'react';
import { render, screen } from '#testing-library/react';
import { composeStories } from '#storybook/testing-react';
import * as stories from './Events.stories'
const { WithSeasonsAndEvents } = composeStories(stories);
describe('Events Screen', ()=> {
render(<WithSeasonsAndEvents />);
it('renders input data', ()=> {
// set up test
});
});
And my stories file:
// Events.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import Events from './Events';
export default {
title: 'Events/Events Screen',
component: Events
} as ComponentMeta<typeof Events>;
const Template: ComponentStory<typeof Events> = (args) => <Events {...args} />;
export const WithSeasonsAndEvents = Template.bind({});
WithSeasonsAndEvents.args = {
// many args here
};
This story renders fine in Storybook. useRouter() works as expected inside my story and I am able to use all of its properties including pathname and locale. However, for some reason the useRouter() function returns null when the composed story is being rendered by React Testing Library.
What I have tried:
Verified installing #storybook/testing-react and set up my global configuration in jest setup file:
// jest-setup.ts
import '#testing-library/jest-dom/extend-expect';
import { setGlobalConfig } from '#storybook/testing-react';
// Storybook's preview file location
import * as globalStorybookConfig from './.storybook/preview';
setGlobalConfig(globalStorybookConfig);
Verified that jest reads my setup file and finds my Storybook preview file
Verified both .storybook/preview.js and .storybook/main.js match the usage guide:
preview.js
// .storybook/preview.js
import '../styles/tailwind.css';
import { RouterContext } from "next/dist/shared/lib/router-context"; // next 12 https://storybook.js.org/docs/react/writing-stories/parameters
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
nextRouter: {
Provider: RouterContext.Provider,
locale: 'en',
},
}
main.js
// .storybook/main.js
module.exports = {
"stories": [
"../stories/**/*.stories.mdx",
"../stories/**/*.stories.#(js|jsx|ts|tsx)",
"../components/**/*.stories.#(js|jsx|ts|tsx|mdx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"storybook-addon-next-router",
],
"framework": "#storybook/react"
}
Stories render fine in Storybook, all useRouter() features work, and when I console.log the useRouter() return value, I get the full suite of NextJS useRouter object properties:
// >> console.log(useRouter()); inside Storybook
{
route: "/",
pathname: "/",
query: {},
asPath: "/",
events: {},
isFallback: false,
locale: 'en'
}
However, on the unit test, when logging the return value of useRouter(); inside my component, it returns null. Understandably, because it is null, the { locale } variable assignment is throwing an error in my unit test.
When logging the value of useRouter variable, both inside my Storybook preview and inside my unit test I get the following function:
// >> console.log(useRouter.toString())
function useRouter() {
return _react.default.useContext(_routerContext.RouterContext);
}
Does anyone have any idea on what can be going wrong? I'm fairly new to Storybook but I've tried looking through GitHuB issues and online and haven't been able to solve. No idea why useRouter() returns null inside Jest if the composeStories() fn should take care of resolving my Storybook params & args. Any insight would be appreciated.
I was running with the same issue. I solved it by exporting WithNextRouter as a decorator on preview.js
// preview.js
import { WithNextRouter } from 'storybook-addon-next-router/dist/decorators';
export const decorators = [WithNextRouter];

"Cannot use import statement outside a module" got message when I try to mocha

I was using jest now on vue/nuxt.
And I am trying to change TDD by mocha and chai.
Installed mocha though npm i mocha --save-dev
package.json
"scripts": {
"test": "mocha"
},
test/list.spec.json
import { expect } from 'chai';
describe('test', function () {
it('should be rendered', function () {
const comp = [1, 2, 3];
expect(Math.max(...comp)).to.above(1);
});
});
Error message when I typed npm test
import { expect } from 'chai';
^^^^^^
SyntaxError: Cannot use import statement outside a module
What is problem with that ?
You cannot use the import syntax in a Node app (import { expect } from 'chai';) if your project isn't properly configured to use this newer syntax. The original Node syntax pre JS modules is const variable = require('./folder/file')
You have to set up Jest or the project you work on to work with ES6 Modules. In package.json, add "type": "module". Here's an explanation: https://www.geeksforgeeks.org/how-to-use-an-es6-import-in-node-js/

Create React App and JEST property matchers

I've got this component that I've simplified for this example as part of a bigger create react app
import React from 'react';
function myVersion() {
return (
<p className={'ActivateSound--para'} data-testid={`version`}>
Version {process.env.REACT_APP_VERSION} has loaded, congrats!
</p>
);
}
export { myVersion };
and in the env I get the version from package.json
REACT_APP_VERSION=$npm_package_version
but in the test I've got
import React from 'react';
import { myVersion } from '../components/my-version';
import { render, screen, cleanup, fireEvent } from '#testing-library/react';
afterEach(cleanup);
describe('my version component', () => {
test('snapshot hasnt changed', () => {
expect(container.firstChild).toMatchSnapshot();
});
});
I am now looking at https://jestjs.io/docs/snapshot-testing#property-matchers. It seems I can exclude the version number somehow, I've tried a bunch of attempts but it's not ignoring the version number
expect(container.firstChild).toMatchSnapshot({
screen.getByTestId('version'): expect.any(String)
});

Test callback prop with Enzyme

I have React-Spring animation in my component:
<SpinnerKf state={status} onRest={changeView && status === 'SUCCESS' ? () => changeView(VIEW_MODES.RECEIPT) : null}>
....
</SpinnerKf>
Where I pass function call inside onRest prop - this is the prop from React-Spring Keyframe, which is called after animation end.
How can I cover this with a test? I'm opened for any tricks, just need to avoid complaining in test coverage.
You can use Enzyme to get the SpinnerKf component and then call its onRest property directly.
Here is a simplified example:
code.js
import * as React from 'react';
const SpinnerKf = () => null;
export const Component = () => (<SpinnerKf onRest={() => { return 'does something'; }}/>);
code.test.js
import * as React from 'react';
import { shallow } from 'enzyme';
import { Component } from './code';
test('callback', () => {
const wrapper = shallow(<Component />);
const result = wrapper.find('SpinnerKf').props().onRest();
expect(result).toBe('does something'); // Success!
});
Note that testing the return value or behavior of the callback is optional, as long as it runs during a unit test it will be included in the code coverage report.

Mock dependencies with Mocha and Typescript

I have a typescript project which uses mocha. Let's say we have two modules as follows.
// http.ts
export class Http {
}
// app.ts
import Http from './http';
export class App {
}
How could I mock the Http module when I'm testing the App?
The test is executed through the npm script as below.
"test": "cross-env NODE_ENV=test ./node_modules/mocha/bin/mocha",
And the mocha options (mocha.opts) looks like below.
test/setup.ts
--compilers ts:ts-node/register
--compilers tsx:ts-node/register
./src/**/*.spec.ts
ts-mock-imports gives you run time control over your imports and maintains type safety.
in app.spec.ts
import * as httpModule from 'http';
import { App } from '../src/app';
import { ImportMock } from 'ts-mock-imports';
const httpMock = ImportMock.mockClass(httpModule, 'Http');
const app = new App(); // App now uses a fake version of the Http class
Now you can control how the Http module behaves in your tests.
Mock the response to a get:
httpMock.mock('get', { data: true });
const response = app.makeGetRequest(); // returns { data: true }
The import statement in typescript is compiled to require. You can use proxyquire to mock any dependencies in your tests

Resources