hello I have a simple app that connects with a topic and then updating he's state, seems to be working when playing around, but I have a problem with how to properly write a test for it
There is a library I using https://www.npmjs.com/package/pubsub-js
import React, { useState, useEffect } from 'react'
import PubSub from 'pubsub-js'
const App = () => {
const [data, setData] = useState([])
const mySub = (msg, pubData) => {
setData(pubData)
}
useEffect(() => {
const token = PubSub.subscribe('TOPIC', mySub);
return () => {
PubSub.unsubscribe(token)
};
},[]);
return (
<input onChange={() => {}} value={data && data[0]?.value}
)
};
import React from 'react'
import PubSub from 'pubsub-js'
import { mount } from 'enzyme'
import App from '.'
const mocked = [{ value: 1}]
describe('should receive some date', () => {
it('xx', () => {
const app = mount(<App />)
PubSub.publish('TOPIC', mocked);
PubSub.publishSync('TOPIC', mocked);
// WHAT NEXT?
})
})
I've tried some
spies, finding input and checking values, but nothing change... why?
How should I test this Function component?
Since the PubSub.publishSync() operation will trigger mySub event handler that updates the state of the component. Make sure you wrap the code that causes React state updates into act(), otherwise, you will get a warning:
Warning: An update to App inside a test was not wrapped in act(...).
E.g.
index.jsx:
import React, { useState, useEffect } from 'react';
import PubSub from 'pubsub-js';
export const App = () => {
const [data, setData] = useState('a');
const mySub = (msg, pubData) => {
setData(pubData);
};
useEffect(() => {
const token = PubSub.subscribe('TOPIC', mySub);
return () => {
PubSub.unsubscribe(token);
};
}, []);
return <div>{data}</div>;
};
index.test.jsx:
import React from 'react';
import PubSub from 'pubsub-js';
import { mount } from 'enzyme';
import { App } from './';
import { act } from 'react-dom/test-utils';
describe('67422968', () => {
it('should receive data and update the state', () => {
const wrapper = mount(<App />);
expect(wrapper.text()).toBe('a');
act(() => {
PubSub.publishSync('TOPIC', 'b');
});
expect(wrapper.text()).toBe('b');
});
});
test result:
PASS examples/67422968/index.test.tsx (7.254 s)
67422968
✓ should receive data and update the state (28 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 91.67 | 100 | 75 | 90.91 |
index.tsx | 91.67 | 100 | 75 | 90.91 | 15
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 7.902 s, estimated 8 s
package versions:
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"pubsub-js": "^1.9.3"
Related
There is a great typescript fetch hook I'd like to mock it.
Here: https://usehooks-ts.com/react-hook/use-fetch
My app basically looks like this:
export default function App() {
const { data, error } = useFetch<InterfaceOfTheResponse>(FETCH_URL)
if (error) return <p>Error</p>
if (!data) return <p>Loading...</p>
return (
<div className="App">
<h1>Welcome</h1>
//data.map... etc
</div>
)
}
My test looks like this:
import { mockData } from "../__mocks__/useFetch"
const mockConfig = {
data: mockData,
error: false,
}
jest.mock("../../customHooks/useFetch", () => {
return {
useFetch: () => mockConfig
}
})
describe("Main page functionality", () => {
test("Renders main page, Welcome", async () => {
const { findByText } = render(<App />)
const WELCOME = await findByText(/Welcome/)
expect(WELCOME).toBeInTheDocument()
})
})
I've tried a couple of ways to mock it, this is the closest what I think it should work, but it's (obviously) not. It says, displays (in the test screen.debug()) the "Error" if statement, and even when I left out form the component the if error check, the "data" is undefined. So what am I doing wrong?
Don't mock the implementation of the useFetch hook, you may break its functions. Instead, we should mock the fetch API of the browser and its response.
E.g.
App.tsx:
import React from 'react';
import { useFetch } from 'usehooks-ts'
const FETCH_URL = 'http://localhost:3000/api';
export default function App() {
const { data, error } = useFetch<any[]>(FETCH_URL)
console.log(data, error);
if (error) return <p>Error</p>
if (!data) return <p>Loading...</p>
return (
<div className="App">
<h1>Welcome</h1>
{data.map(d => <div key={d}>{d}</div>)}
</div>
)
}
App.test.tsx:
import { render, screen } from "#testing-library/react";
import '#testing-library/jest-dom';
import React from "react";
import App from './App';
describe('74144869', () => {
test('should pass', async () => {
const mData = [1, 2]
const mResponse = {
ok: true,
json: jest.fn().mockResolvedValue(mData)
}
global.fetch = jest.fn().mockResolvedValue(mResponse as unknown as Response);
render(<App />);
expect(await screen.findByText(1)).toBeInTheDocument();
})
});
Test result:
PASS stackoverflow/74144869/App.test.tsx (11.11 s)
74144869
✓ should pass (58 ms)
console.log
undefined undefined
at App (stackoverflow/74144869/App.tsx:7:11)
console.log
undefined undefined
at App (stackoverflow/74144869/App.tsx:7:11)
console.log
[ 1, 2 ] undefined
at App (stackoverflow/74144869/App.tsx:7:11)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 91.67 | 75 | 100 | 100 |
App.tsx | 91.67 | 75 | 100 | 100 | 9
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.797 s, estimated 12 s
package versions:
"usehooks-ts": "^2.9.1",
"#testing-library/react": "^11.2.7",
"#testing-library/jest-dom": "^5.11.6",
I am looking for a way to test my hook for React components:
export default function useKeyUp(key: Key, onKeyUp: Function) {
useEffect(() => {
const handleUp = (event: KeyboardEvent) => {
const { key: releasedKey } = event
if (key === releasedKey) {
if (onKeyUp) {
onKeyUp()
}
}
}
window.addEventListener('keyup', handleUp)
return () => {
window.removeEventListener('keyup', handleUp)
}
}, [key, onKeyUp])
}
How can I mock / simulate event fired on the Window object in Jest / Enzyme?
Since enzyme can't test react hook directly like react-hooks-testing-library. We can use the hook inside a component. Then, use mount to render the component.
Use keyboardEvent constructor to create a "keyup" event and dispatch the event via dispatchEvent web API.
E.g.
useKeyUp.ts:
import { useEffect } from 'react';
type Key = string;
export default function useKeyUp(key: Key, onKeyUp: Function) {
useEffect(() => {
const handleUp = (event: KeyboardEvent) => {
const { key: releasedKey } = event;
if (key === releasedKey) {
if (onKeyUp) {
onKeyUp();
}
}
};
window.addEventListener('keyup', handleUp);
return () => {
window.removeEventListener('keyup', handleUp);
};
}, [key, onKeyUp]);
}
useKeyUp.test.tsx:
import { mount } from 'enzyme';
import React from 'react';
import useKeyUp from './useKeyUp';
describe('70938281', () => {
test('should call onKeyUp callback', () => {
const mOnKeyUp = jest.fn();
function TestComp() {
useKeyUp('s', mOnKeyUp);
return null;
}
const wrapper = mount(<TestComp />);
const keyUpEvent = new KeyboardEvent('keyup', { key: 's' });
window.dispatchEvent(keyUpEvent);
expect(mOnKeyUp).toBeCalledTimes(1);
wrapper.unmount();
window.dispatchEvent(keyUpEvent);
expect(mOnKeyUp).toBeCalledTimes(1);
});
test('should NOT call onKeyUp callback', () => {
const mOnKeyUp = jest.fn();
function TestComp() {
useKeyUp('s', mOnKeyUp);
return null;
}
mount(<TestComp />);
const keyUpEvent = new KeyboardEvent('keyup', { key: 'a' });
window.dispatchEvent(keyUpEvent);
expect(mOnKeyUp).not.toBeCalled();
});
});
Test result:
PASS stackoverflow/70938281/useKeyUp.test.tsx (8.484 s)
70938281
✓ should call onKeyUp callback (29 ms)
✓ should NOT call onKeyUp callback (2 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 75 | 100 | 100 |
useKeyUp.ts | 100 | 75 | 100 | 100 | 9
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.937 s
jest.config.js:
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'enzyme',
setupFilesAfterEnv: ['jest-enzyme', 'jest-extended'],
setupFiles: ['./jest.setup.js'],
testEnvironmentOptions: {
enzymeAdapter: 'react16',
},
};
I have a React Scroll to Top component where we add this component Below our Router so that while moving across page we donot maintain scroll position .
I was trying to write Test case for this Component But Jest and Enzyme dosent seem to recognise this as a component when doing shallow rendering. I am using typescript and this is the component.
scrollToTop.ts
export const ScrollToTop = ({history}: IRouterResetScroll) => {
useEffect(() => {
const unListen = history.listen(() => {
window.scrollTo(0, 0);
});
return () => {
unListen();
}
}, []);
return null;
}
export default withRouter(ScrollToTop);
Here is my unit test strategy, the hardest part for your code to be tested is the history.listen(handler), so we can mock the implementation of history.listen method, we defined a queue to store the handlers. After mount the component, the mocked history will execute history.listen with a function as parameter. This function will be stored in the queue we defined before. We can get this function from the queue in unit test case and trigger it manually.
index.tsx:
import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';
type IRouterResetScroll = any;
export const ScrollToTop = ({ history }: IRouterResetScroll) => {
useEffect(() => {
const unListen = history.listen(() => {
window.scrollTo(0, 0);
});
return () => {
unListen();
};
}, []);
return null;
};
export default withRouter(ScrollToTop);
index.spec.tsx:
import React from 'react';
import { ScrollToTop } from './';
import { mount } from 'enzyme';
describe('ScrollToTop', () => {
it('should scroll to top', () => {
const queue: any[] = [];
const mUnListen = jest.fn();
const mHistory = {
listen: jest.fn().mockImplementation(fn => {
queue.push(fn);
return mUnListen;
})
};
window.scrollTo = jest.fn();
const wrapper = mount(<ScrollToTop history={mHistory}></ScrollToTop>);
queue[0]();
expect(mHistory.listen).toBeCalledWith(expect.any(Function));
expect(window.scrollTo).toBeCalledWith(0, 0);
wrapper.unmount();
expect(mUnListen).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58786973/index.spec.tsx
ScrollToTop
✓ should scroll to top (39ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.041s, estimated 9s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58786973
I am writing an Electron app, using React for the front-end and JEST + React Testing Library for running tests. I have the following simplified code in a module:
import React from 'react';
import { ipcRenderer } from 'electron';
import Paper from '#material-ui/core/Paper';
import LinearProgress from '#material-ui/core/LinearProgress';
const AccountCheckModule = () => {
const [listingsCount, setListingsCount] = React.useState(0);
React.useEffect(() => {
ipcRenderer.on('count-listings', (event, count) => {
setListingsCount(count);
});
ipcRenderer.send('count-listings');
// Cleanup the listener events so that memory leaks are avoided.
return function cleanup() {
ipcRenderer.removeAllListeners('count-listings');
};
}, []);
return (
<Paper elevation={2} data-testid="paper">
<p
className={classes.listingsNumberTracker}
data-testid="free-listings-counter"
>
Free listings: {listingsCount}/100
</p>
<BorderLinearProgress
className={classes.margin}
variant="determinate"
color="secondary"
value={listingsCount}
data-testid="border-linear-progress"
/>
</Paper>
);
};
export default AccountCheckModule;
Basically, React.useEffect() runs once, calls ipcRenderer.send('count-listings'); and sets up a listener to wait for the response from the main process. The main process responds with a listings count number and when received is used to update the listingsCount state -> setListingsCount(count)
Is it possible to mock this listener function to return a 'count' number using Jest.
ipcRenderer.on('count-listings', (event, count) => {
setListingsCount(count);
});
If yes, how would you go about achieving this?
Here is a unit test solution, I create a simple electron module to simulate the real electron node module and simplify your component JSX element.
E.g.
index.tsx:
import React from 'react';
import { ipcRenderer } from './electron';
const AccountCheckModule = () => {
const [listingsCount, setListingsCount] = React.useState(0);
React.useEffect(() => {
ipcRenderer.on('count-listings', (event, count) => {
setListingsCount(count);
});
ipcRenderer.send('count-listings', 2);
// Cleanup the listener events so that memory leaks are avoided.
return function cleanup() {
ipcRenderer.removeAllListeners('count-listings');
};
}, []);
return <div>{listingsCount}</div>;
};
export default AccountCheckModule;
electron.ts:
export const ipcRenderer = {
events: {},
on(event, handler) {
this.events[event] = handler;
},
send(event, data) {
this.events[event](event, data);
},
removeAllListeners(event) {
this.events[event] = undefined;
}
};
index.spec.tsx:
import React from 'react';
import { render, act } from '#testing-library/react';
import { ipcRenderer } from './electron';
import AccountCheckModule from './';
describe('AccountCheckModule', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('should render correct', async () => {
const events = {};
const onSpy = jest.spyOn(ipcRenderer, 'on').mockImplementation((event, handler) => {
events[event] = handler;
});
const sendSpy = jest.spyOn(ipcRenderer, 'send').mockImplementation((event, data) => {
events[event](event, data);
});
const { getByText, container } = render(<AccountCheckModule></AccountCheckModule>);
const mCount = 666;
act(() => {
ipcRenderer.send('count-listings', mCount);
});
const element = getByText(mCount.toString());
expect(element).toBeDefined();
expect(onSpy).toBeCalledWith('count-listings', expect.any(Function));
expect(sendSpy).toBeCalledWith('count-listings', mCount);
expect(container).toMatchSnapshot();
});
});
Unit test result with 100% coverage report for SFC:
PASS src/stackoverflow/58048849/index.spec.tsx
AccountCheckModule
✓ should render correct (47ms)
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 88.89 | 100 | 71.43 | 87.5 | |
electron.ts | 50 | 100 | 33.33 | 50 | 4,7 |
index.tsx | 100 | 100 | 100 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 passed, 1 total
Time: 4.247s, estimated 11s
index.spec.tsx.snap:
// Jest Snapshot v1
exports[`AccountCheckModule should render correct 1`] = `
<div>
<div>
666
</div>
</div>
`;
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58048849
I hope someone can point me in the right direction to test useRef in the component below.
I have a component structured something like below. I am trying to test the functionality within the otherFunction() but I'm not sure how to mock the current property that comes off the component ref. Has anyone done something like this before?
const Component = (props) => {
const thisComponent = useRef(null);
const otherFunction = ({ current, previousSibling }) => {
if (previousSibling) return previousSibling.focus();
if (!previousSibling && current) return current.focus();
}
const handleFocus = () => {
const {current} = thisComponent;
otherFunction(current);
}
return (
<div ref={thisComponent} onFocus={handleFocus}>Stuff In here</div>
);
};
Here is my test strategy for your case. I use jest.spyOn method to spy on React.useRef hook. It will let us mock the different return value of ref object for SFC.
index.tsx:
import React, { RefObject } from 'react';
import { useRef } from 'react';
export const Component = props => {
const thisComponent: RefObject<HTMLDivElement> = useRef(null);
const otherFunction = ({ current, previousSibling }) => {
if (previousSibling) return previousSibling.focus();
if (!previousSibling && current) return current.focus();
};
const handleFocus = () => {
const { current } = thisComponent;
const previousSibling = current ? current.previousSibling : null;
otherFunction({ current, previousSibling });
};
return (
<div ref={thisComponent} onFocus={handleFocus}>
Stuff In here
</div>
);
};
index.spec.tsx:
import React from 'react';
import { Component } from './';
import { shallow } from 'enzyme';
describe('Component', () => {
const focus = jest.fn();
beforeEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
});
test('should render correctly', () => {
const wrapper = shallow(<Component></Component>);
const div = wrapper.find('div');
expect(div.text()).toBe('Stuff In here');
});
test('should handle click event correctly when previousSibling does not exist', () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: { focus } });
const wrapper = shallow(<Component></Component>);
wrapper.find('div').simulate('focus');
expect(useRefSpy).toBeCalledTimes(1);
expect(focus).toBeCalledTimes(1);
});
test('should render and handle click event correctly when previousSibling exists', () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: { previousSibling: { focus } } });
const wrapper = shallow(<Component></Component>);
wrapper.find('div').simulate('focus');
expect(useRefSpy).toBeCalledTimes(1);
expect(focus).toBeCalledTimes(1);
});
test('should render and handle click event correctly when current does not exist', () => {
const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: null });
const wrapper = shallow(<Component></Component>);
wrapper.find('div').simulate('focus');
expect(useRefSpy).toBeCalledTimes(1);
expect(focus).not.toBeCalled();
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/56739670/index.spec.tsx (6.528s)
Component
✓ should render correctly (10ms)
✓ should handle click event correctly when previousSibling does not exist (3ms)
✓ should render and handle click event correctly when previousSibling exists (1ms)
✓ should render and handle click event correctly when current does not exist (2ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 7.689s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56739670