I am facing a very strange situation. I have 9 unit tests that are running perfectly well when I run it through jest command. But when I run storybook it just shows 3 tests out of 9. Storybook version is 5.1.7.
Test which is showing on storybook - Filter Search Orders -
import React from "react";
import expect from "expect";
import { mount } from "enzyme";
import _ from "lodash";
import Root from "../../root";
import { storiesOf, describe, it, specs, beforeEach, afterEach } from "../../../.storybook/facade";
import FilterSearchOrders from "../search-orders/filter-search-orders";
import ordersFilterOptions from "../search-orders/orders-filters-options";
const filterSearchOrdersInfo = {
searchedText: "",
status: "",
pageNo: 0,
};
const getElement = () => {
return (
<Root>
<FilterSearchOrders
searchedText={filterSearchOrdersInfo.searchedText}
status={filterSearchOrdersInfo.status}
pageNo={filterSearchOrdersInfo.pageNo}
/>
</Root>
);
};
storiesOf("Filter Search Orders", module).add("Filter Search Orders", () => {
specs(() =>
describe("Test case for filter search order component", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(getElement());
});
afterEach(() => {
wrapper.unmount();
});
it("should render an input box for searching", () => {
expect(wrapper.find(".search-orders-input"));
});
it("should render a dropdown with options", () => {
const dropdownOptions = wrapper.find(".item").map((node) => node.text());
const items = _.map(ordersFilterOptions, _.property("text"));
expect(dropdownOptions).toEqual(items);
});
it("should render a primary button called search", () => {
expect(wrapper.find(".primary").text()).toEqual("SEARCH");
});
})
);
return getElement();
});
Test not showing on storybook - Menu story -
import React from "react";
import expect from "expect";
import { mount } from "enzyme";
import _ from "lodash";
import { DASHBOARDITEMS } from "../menu/menuItems";
import { storiesOf, describe, it, specs } from "../../../.storybook/facade";
import NavBar from "../menu";
import Root from "../../root";
const getElement = () => {
return (
<Root>
<NavBar items={DASHBOARDITEMS} />
</Root>
);
};
storiesOf("Menu", module).add("Navigation Bar component", () => {
specs(() =>
describe("Main menu", () => {
it("should render menu", () => {
const wrapper = mount(getElement());
expect(wrapper.find(".nestedMenu"));
});
it("should render an image to show logo", () => {
const wrapper = mount(getElement());
expect(
wrapper
.find(".item")
.at(0)
.find("img").length > 0
).toEqual(true);
});
it("should render all links", () => {
const wrapper = mount(<Root>{getElement()}</Root>);
const links = wrapper.find("a").map((node) => node.text());
const items = _.map(DASHBOARDITEMS, _.property("content"));
expect(links).toEqual(items);
});
})
);
return getElement();
});
Okay so I seemed to figure out what was wrong the whole time.
A very stupid mistake by me but I hope it helps anyone who is dealing with same issue.
I checked the console and seems like the fourth story (in order) that I had written had some error and as soon as I resolved that error that story itself and all other story started showing on storybook.
I upgraded my storybook version and seems like all the stories are shown on the UI with errors.
Related
If anyone can help, I have a custom hook that uses ResizeObserver to change the width of a component. My problem is that when I go to run my units test it breaks all my tests and looking at the snapshot it is not rendering all the elements in the dom. It was working before until I implemented the ResizeObserver. Does anyone know if there is a way I jest.mock the ResizeObserver to not undefined. Or other suggestions.
import * as React from 'react';
import ResizeObserver from 'resize-observer-polyfill';
const useResizeObserver = (ref: { current: any }) => {
const [dimensions, setDimensions] = React.useState<DOMRectReadOnly>();
React.useEffect(() => {
const observeTarget = ref.current;
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
setDimensions(entry.contentRect);
});
});
resizeObserver.observe(observeTarget);
return () => {
resizeObserver.unobserve(observeTarget);
};
}, [ref]);
return dimensions;
};
export default useResizeObserver;
import { render, screen, waitFor } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import React from 'react';
import mockFetchProfileActivity from '../../../services/mocks/fetch-profile-activity';
import BarChart from './BarChart';
const component = <BarChart userActivity={mockFetchProfileActivity} />;
describe('Render barElement Chart component', () => {
const observers: any[] = [];
let resizeHandler: (observers: any[]) => void;
(window as any).ResizeObserver = (e: any) => {
resizeHandler = e;
return {
observe(element: any) {
observers.push(element);
},
unobserve(element: any) {
const i = observers.indexOf(element);
if (i !== -1) {
observers.splice(i, 1);
}
}
};
};
it('Matches the snapshot', () => {
// resizeHandler(observers);
const container = render(component);
expect(container).toMatchSnapshot();
});
it('when clicking on a chart barElement drilldown "challenges" are shown', async () => {
// arrange
const componentRender = render(component);
waitFor(() => resizeHandler(observers));
// act
const barElement = componentRender.container.querySelector('svg rect');
if (barElement) userEvent.click(barElement);
// assert
expect(screen.getByText('Challenge 1')).toBeInTheDocument();
});
});
I chose to add the polyfill as a dev dependency and add the following line to setupTests.js/ts:
global.ResizeObserver = require('resize-observer-polyfill')
Mock the ResizeObserver:
class ResizeObserver {
observe() {
// do nothing
}
unobserve() {
// do nothing
}
disconnect() {
// do nothing
}
}
window.ResizeObserver = ResizeObserver;
export default ResizeObserver;
sample.test.js
import ResizeObserver from './__mocks__/ResizeObserver';
import module from 'sample';
describe('module', ()=> {
it('returns an instance of ResizeObserver', () => {
// do something that uses the resize observer
// NOTE: The actual observe handler would not be called in jsdom anyway as no resize would be triggered.
// e.g.
expect(module.somethingThatReturnAReference to the resize observer).toBeInstanceOf(ResizeObserver);
});
});
source
I've added to setupTests.js/ts next code:
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}))
Edit:
Add these lines below the imports above the describe method
Building upon the already excellent answers, here is what I did to get my React Testing library tests running
Take a dependency on the required polyfill in package.json
"devDependencies": {
...
"resize-observer-polyfill": "^1.5.1",
...
}
Update the setupTests.ts file as follow.
import * as ResizeObserverModule from 'resize-observer-polyfill';
(global as any).ResizeObserver = ResizeObserverModule.default;
Now your tests should run fine.
I had similar issue using Create React App setup.
If that is your case, you can create a file in your root directory called setupTest.js and add the following code:
import '#testing-library/jest-dom/extend-expect';
import 'jest-extended';
jest.mock('./hooks/useResizeObserver', () => () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
})),
}));
You can find more information to configure the test environment for Create React App here and the ResizeObserver API here
I have the same problem in my React project. I solved this problem by following these steps below.
npm i -D resize-observer-polyfill
global.ResizeObserver = require("resize-observer-polyfill"); under the setupTests.ts
Hey rather than downloading a polyfill you can follow this approach
class ResizeObserver {
constructor(observerCallback) {
this.observerCallback = observerCallback;
}
observe = () => {
// using actual dom element as mutation observer requires
// an actual node in dom
const scrollContainer = document.querySelector(
'.horizontal-scroll-view__items',
);
// Mutation observer observer any changes in DOM tree
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes') {
this.observerCallback();
}
});
});
observer.observe(scrollContainer, { attributes: true });
};
}
global.ResizeObserver = ResizeObserver;
With mutation observer you can hardcodingly resize your div and its attributes would be monitored with mutation observer. Hence it will result in callback trigger.
I'm trying to write a test for the following:
import React from 'react'
import Popup from 'some-library'
const popupConfig = {
home: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
},
settings: {
popupValue: 'Hello World',
popupValue: 'action',
popupMessage: 'Get Started'
}
}
const closePopup = () => {
Popup.closePopup()
}
const toggleNewPopup = () => {
Popup.togglePopup('some-popup')
}
const GetStartedPopup = ({ moduleName }) => {
if (!Object.keys(popupConfig).includes(moduleName)) return null
const {
popupValue = 'Hi there!',
popupStyle = 'warning',
popupMessage = 'Get Started',
popupBtnFunction = toggleNewPopup
} = popupConfig[moduleName]
return (
<Popup
popupValue={popupValue}
popupStyle={popupStyle}
popupBtnValue={popupMessage}
popupBtnStyle="neutral"
popupBtnFunction={popupBtnFunction}
xPopup={closePopup}
/>
)
}
export default GetStartedPopup
The objective of the test is to make sure that the closePopup and toggleNewPopup functions are called. I'm doing the following to do that for the closePopup function:
import React from 'react'
import { mount } from 'enzyme'
import { Popup } from 'some-library'
import GetStartedPopup from 'widgets/getStartedPopup'
describe('<GetStartedPopup/>', () => {
let wrapper
let props
beforeEach(() => {
props = {
page: 'home'
}
wrapper = mount(<GetStartedPopup {...props}/>)
})
it('should render the component without crashing', () => {
expect(wrapper).toBeDefined();
})
it('should call closePopup', () => {
const spy = jest.spyOn(wrapper.instance(), 'closePopup');
wrapper.instance().closePopup();
expect(spy).toHaveBeenCalledTimes(1);
})
afterEach(() => {
wrapper.unmount()
})
})
I went through the docs for spyOn and other SO threads that tackle issues like this but couldn't resolve how to test the closePopup and toggleNewPopup functions for my case here. When I run the test case written above I get this: TypeError: Cannot read property 'closePopup' of null. What would be the correct way to write the test to make sure that the two functions are called?
Funny that I ran into this myself at work in regards to wrapper.instance() doc
To return the props for the entire React component, use wrapper.instance().props. This is valid for stateful or stateless components in React 15.. But, wrapper.instance() will return null for stateless React component in React 16., so wrapper.instance().props will cause an error in this case.
As for the 3rd party library. You should be mocking any collaborators that your component uses.
import { Popup } from 'some-library';
describe('<GetStartedPopup />', () => {
let wrapper;
jest.mock('some-library', () => {
Popup: jest.fn(),
});
const initialProps = {
page: 'home'
};
const getStartedPopup = () => {
return mount(<GetStartedPopup {...initialProps});
};
beforeEach(() => {
Popup.mockClear()
wrapper = getStartedPopup();
};
it('should call closePopup', () => {
expect(Popup.closePopup()).toHaveBeenCalledTimes(1);
});
...
});
I tried to implement mount from enzyme in the following but found wrapper.debug() is empty.
import {mount} from 'enzyme';
const Foo = () => <div>Sairam</div>;
describe(...
it(...
const wrapper = mount(<Foo/>);
console.log(wrapper.debug()) // gives empty value
UPDATE:
import {
specs, describe, it,
beforeEach, before, after, afterEach, xdescribe
} from 'storybook-addon-specifications';
import {mount, shallow} from 'enzyme';
storiesOf('UI|Components', module)
.add('with text', () => {
const Foo = () => <div>Sairam</div>;
specs(() =>
describe('with text', () => {
it('Clicking on the showFirst button should select the first page', () => {
const wrapper = mount(<Foo/>);
console.log("WRAPPER TEST" , wrapper.debug());
// expect(wrapper.props().chipData).to.equal(ChipData);
wrapper.unmount();
expect(2).toBe(23);
});
})
);
return story;
})
The Reason why it is failed for me is because of adding decorator
addDecorator(StoryRouter()); to my .storybookconfig file
how can i test the child component onclick.
Please see the below snippet.
// App.js
import React, {Component, Fragment} from 'react'
import Child from './child'
class App extends Component{
state = {
data: null,
enable: false
}
componentDidMount(){
this.getData()
}
getData = async () => {
const response = await fetch('http://www.example.com');
const data = await response.json();
this.setState({
data
})
}
_handleChildClick = () => {
this.setState({
enable: true
})
}
render(){
const {data, enable} = this.state
if(!data){
return (
<div>
Loading
</div>
)
}else{
<Fragment>
<Child
handleChildClick={this._handleChildClick}
/>
</Fragment>
}
}
}
export default App
import React from 'react';
const child = () => {
return(
<div>
<button
className="toggle"
onClick={props.handleChildClick}
>
Toggle
</button>
</div>
)
}
export default child
// App.test.js
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it('should trigger _handleChildClick', async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () => new Promise((resolve, reject) => {
resolve(
{
name: "some data"
}
)
})
}))
const mountWrapper = await mount(<App />)
setTimeout(() => {
mountWrapper.update()
const SpyhandleChildClick = jest.spyOn(mountWrapper.instance(),'_handleChildClick')
mountWrapper.find('.toggle').simulate('click')
expect(SpyhandleChildClick).toHaveBeenCalled() // not called
},0)
})
})
Some important points to consider.
Asynchronous code in your tests
If you have to do asynchronous tasks in your tests you always have to await until the asynchronous stuff is completed.
setTimeout(() => {
mountWrapper.update()
const SpyhandleChildClick = jest.spyOn(mountWrapper.instance(),'_handleChildClick')
mountWrapper.find('.toggle').simulate('click')
expect(SpyhandleChildClick).toHaveBeenCalled() // not called
},0)
Above in your code you have a timeout segment. Any test condition inside this code block will not be evaluated since by the time they are evaluated you 'test session' will already be over due to the aync nature.
Testing arrow functions in React with enzyme - forceUpdate()
There seem to be a problem with the enzyme library where you have to force update the react component after spying for it to latch on to the method.
Please follow the github issue for more information : https://github.com/airbnb/enzyme/issues/365
I also cleaned up your test code a bit to make it more understandable!
// App.test.js
import React from 'react';
import {enzyme} from 'enzyme';
import App from './App';
describe("App test cases", () => {
it("should trigger _handleChildClick", async () => {
window.fetch = jest.fn().mockImplementation(() => ({
status: 200,
json: () =>
new Promise((resolve, reject) => {
resolve({
name: "some data"
});
})
}));
const mountWrapper = mount(<App />);
mountWrapper.update();
console.log("mountWrapper", mountWrapper.debug()); // showing the loader one
//[FIX]This code will block and wait for your asynchronous tasks to be completed
await new Promise(res => setTimeout(() => res(), 0));
mountWrapper.update();
console.log("mountWrapper", mountWrapper.debug()); // nothing showing
expect(mountWrapper.find(".toggle").length).toEqual(1);
//[FIX]Get a reference from the wrapper and force update after the spyOn call
const instance = mountWrapper.instance();
const spy = jest.spyOn(instance, "_handleChildClick");
instance.forceUpdate();
mountWrapper.find(".toggle").simulate("click");
expect(spy).toHaveBeenCalled();
});
});
Live Demo Link: Click on the 'Tests' tab on the browser to see the test results
https://codesandbox.io/s/mz21kpm37j
I wrote a simple unit test for the following. I am new to React JS testing - Trying to run a test using jest and enzyme.
render() {
return (
<div>
<div className="not-found">
<div className='_2'>WAS NOT FOUND</div>
<div onClick={() => {window.history.back()}} className='not-found-
btn' href='/'>GO BACK</div>
)
}
}
The file looks simple, there are no props and the only thing not being covered when the test is running is onClick . How could I test onClick and make sure the test is 100 % covered. Thanks
<div onClick={() => {window.history.back()}} className='not-found-
btn' href='/'>GO BACK</div>
file.test.js
// jest mock functions (mocks this.props.func)
const onClick = jest.fn();
// defining this.props
const baseProps = {
onClick,
}
describe(' Test', () => {
let wrapper;
let tree;
beforeEach(() => wrapper = shallow(<Component{...baseProps } />));
// before each test, shallow mount the Component
it('should render correctly', () => {
tree = renderer.create(<NotFound {...baseProps} />)
let treeJson = tree.toJSON()
expect(treeJson).toMatchSnapshot();
tree.unmount()
});
it('calls onClick event ', () => {
const mockOnClick = jest.fn();
const wrapper = shallow(
<NotFound onClick={mockOnClick} className='not-found-btn' />
);
const component = wrapper.shallow();
component.find('GO BACK').simulate('click');
expect(mockOnClick.mock.calls.length).toEqual(1);
I'd avoid using window history and instead use react-router-dom for MPAs. In addition, instead of using an anonymous function, you can use a PureComponent class (it's similar to a Component class, but it doesn't update state) with a method class function.
Working example: https://codesandbox.io/s/j3qo6ppxqy (this example uses react-router-dom and has a mix of integration and unit testing -- see the tests tab at the bottom of the page to run the tests and look for __test__ folders to see the code)
components/NotFound/notfound.js
import React, { PureComponent } from "react";
import { Button } from "antd";
export default class NotFound extends PureComponent {
handlePageBack = () => this.props.history.push("/");
render = () => (
<div className="notfound">
<h1>404 - Not Found!</h1>
<Button type="default" onClick={this.handlePageBack}>
Go Back
</Button>
</div>
);
}
components/NotFound/__tests__/notfound.test.js (as mentioned here, you can also test the class method, if desired)
import React from "react";
import { shallowComponent } from "../../../tests/utils";
import NotFound from "../notfound";
const mockGoBack = jest.fn();
const initialProps = {
history: {
goBack: mockGoBack
}
};
/*
the shallowComponent function below is a custom function in "tests/utils/index.js" that
simplifies shallow mounting a component with props and state
*/
const wrapper = shallowComponent(<NotFound {...initialProps} />);
describe("Not Found", () => {
it("renders without errors", () => {
const notfoundComponent = wrapper.find("div.notfound");
expect(notfoundComponent).toHaveLength(1);
});
it("pushes back a page when Go Back button is clicked", () => {
wrapper.find("Button").simulate("click");
expect(mockGoBack).toHaveBeenCalled();
});
});
window.history.back is being called, but it has a delay time. I can make it work using a Promise:
const Component = ()=> (<div>
<button onClick={()=> window.history.back()} className="btn btn-back">
Back
</button>
</div>)
Component.test.js
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
const delayAction = (fn, time = 1000) =>
new Promise((resolve) => {
fn();
setTimeout(() => {
resolve();
}, time);
});
let container = null;
describe("App tests", () => {
afterEach(() => {
//unmount Component...
});
beforeEach(() => {
//mount Component
});
it("should call history.back()", async (done) => {
const btnBack = container.querySelector(".btn-back");
await act(() =>
delayAction(() => btnBack.dispatchEvent(new MouseEvent("click", { bubbles: true })))
);
// asserts..
done();
});
});