Fire (dispatch) custom event with react-testing-library - reactjs

Is there a way to fire a custom event with react-testing-library? I couldn't find such example in their docs.
useEffect(() => {
document.body.addEventListener('customEvent', onEvent);
}, []);
I want to fire custom event (sth. like fireEvent('customEvent') and test if onEvent was called.

You can use fireEvent to dispatch a CustomEvent on document.body HTML element. I added spy to console.log() method to check if the onEvent event handler is called or not.
E.g.
index.tsx:
import React, { useEffect } from 'react';
export function App() {
useEffect(() => {
document.body.addEventListener('customEvent', onEvent);
}, []);
function onEvent(e) {
console.log(e.detail);
}
return <div>app</div>;
}
index.test.tsx:
import { App } from './';
import { render, fireEvent } from '#testing-library/react';
import React from 'react';
describe('67416971', () => {
it('should pass', () => {
const logSpy = jest.spyOn(console, 'log');
render(<App />);
fireEvent(document.body, new CustomEvent('customEvent', { detail: 'teresa teng' }));
expect(logSpy).toBeCalledWith('teresa teng');
});
});
test result:
PASS examples/67416971/index.test.tsx (8.781 s)
67416971
✓ should pass (35 ms)
console.log
teresa teng
at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
-----------|---------|----------|---------|---------|-------------------
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: 9.638 s
package versions:
"#testing-library/react": "^11.2.2",
"react": "^16.14.0"

Related

How to mock axios call

I have a component Student component as follows
import axios from 'axios';
import React from 'react';
import { useEffect, useState } from 'react';
function Student(props) {
const [studentRecord, setStudentRecord] = useState(props.stRecord);
const [studentSubjects, setStudentSubjects] = useState(null);
function getStudentSubjects() {
let apicalladdress = '/studentapi/GetStudentSubjects/' + studentRecord.studentNumber;
axios.get(apicalladdress).then((result) => {
setStudentSubjects(result.data);
});
}
useEffect(() => {
getStudentSubjects();
}, [studentRecord]);
return (
<div>
<div>{studentRecord.studentNumber}</div>
<div>{setStudentSubjects[0].subjectName}</div>
</div>
);
}
I have a test created as below
import {StudentSubjectsData} from "../globalDataProvider";
import AxiosMock from "axios"
it("make sure student renders",async ()=>{
const mockStudentSubjects=await Promise.resolve({data: StudentSubjectsData()});
AxiosMock.get.mockResolvedValue(mockStudentSubjects);
render (<Student stRecord={StudentRecord}/>);
}
But I am getting following errors
Error 1. An update to Student component inside a test was not wrapped in act(...)
for line :setStudentSubjects(result.data);
Error 2. For following print line, I am getting error TypeError: Cannot read properties of undefined (reading subjectName)
<div>{setStudentSubjects[0].subjectName}</div>
Any suggestions please...
I didn't see how you mock the axios.get() method, I will use jest.spyOn() to mock it.
For the second error, the studentSubjects state is null for the first render. It's better to give an empty array instead of null as its initial value. Besides, you can use optional chain to access the value.
Student.tsx:
import axios from 'axios';
import React from 'react';
import { useEffect, useState } from 'react';
export function Student(props) {
const [studentRecord, setStudentRecord] = useState(props.stRecord);
const [studentSubjects, setStudentSubjects] = useState<{ subjectName: string }[]>([]);
function getStudentSubjects() {
let apicalladdress = '/studentapi/GetStudentSubjects/' + studentRecord.studentNumber;
axios.get(apicalladdress).then((result) => {
setStudentSubjects(result.data);
});
}
useEffect(() => {
getStudentSubjects();
}, [studentRecord]);
return (
<div>
<div>{studentRecord.studentNumber}</div>
<div>{studentSubjects[0]?.subjectName}</div>
</div>
);
}
Student.test.tsx:
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import axios from 'axios';
import React from 'react';
import { Student } from './Student';
describe('75502126', () => {
test('should pass', async () => {
const axioGetSpy = jest.spyOn(axios, 'get').mockResolvedValue({ data: [{ subjectName: 'a' }] });
render(<Student stRecord={{ studentNumber: 1 }} />);
expect(await screen.findByText('a')).toBeInTheDocument();
axioGetSpy.mockRestore();
});
});
Test result:
PASS stackoverflow/75502126/Student.test.tsx (8.98 s)
75502126
✓ should pass (34 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
Student.tsx | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.508 s

Unit test React functional component's function

is there anyway we can unit test a function in a react Functional component. Since wrapper.instance() will return null for functional components what's the best way to include this function in test to get maximum coverage.
const SimpleFC: React.FC = () => {
const callbackFunction = () => {
// Do Stuffs
}
return (
<ChildComponent callback={callbackFunction} />
)
}
export { SimpleFC };
In this code segment how can we invoke the callbackFunction ?
Thanks in advance
Through you are using wrapper.instance() API, I arbitrarily think that you are using the enzyme library. You can use .invoke(invokePropName)(...args) => Any method to invoke a function prop on ChildComponent directly.
E.g.
SimpleFC.tsx:
import React from 'react';
import ChildComponent from './ChildComponent';
const SimpleFC: React.FC = () => {
const callbackFunction = () => {
// Do Stuffs
console.log('Do Stuffs');
};
return <ChildComponent callback={callbackFunction} />;
};
export { SimpleFC };
ChildComponent.tsx:
import React from 'react';
export default function ChildComponent({ callback }) {
return <div onClick={callback}>child component</div>;
}
SimpleFC.test.tsx:
import { shallow } from 'enzyme';
import React from 'react';
import { SimpleFC } from './SimpleFC';
describe('67774847', () => {
it('should pass', () => {
const logSpy = jest.spyOn(console, 'log');
const wrapper = shallow(<SimpleFC />);
wrapper.invoke('callback')();
expect(logSpy).toBeCalledWith('Do Stuffs');
logSpy.mockRestore();
});
});
test result:
PASS examples/67774847/SimpleFC.test.tsx (8.752 s)
67774847
✓ should pass (48 ms)
console.log
Do Stuffs
at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
--------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------------|---------|----------|---------|---------|-------------------
All files | 90 | 100 | 66.67 | 90 |
ChildComponent.tsx | 66.67 | 100 | 0 | 66.67 | 4
SimpleFC.tsx | 100 | 100 | 100 | 100 |
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.654 s

What approach should be used to test a SocketIO Client app using Jest?

I need to test a React Client app using SocketIO Client. I have searched various websites online but couldn't get any of their examples to work. I then installed Express on the client app as a dev dependency and tried to start a test server in the Jest test but couldn't get that to work.
So I was wondering, in fact, what would be the right way to test this app anyway?
My target is to test the following event listener registered in componentDidMount
componentDidMount() {
const current_this = this;
socket.on("numOfPlayersChanged", function(data) {
// do something
});
}
Here is my solution:
index.tsx:
import React, { Component } from 'react';
import io from 'socket.io';
const socket = io();
class SomeComponent extends Component {
constructor(props) {
super(props);
this.handleNumOfPlayersChanged = this.handleNumOfPlayersChanged.bind(this);
}
componentDidMount() {
socket.on('numOfPlayersChanged', this.handleNumOfPlayersChanged);
}
render() {
return <div>some component</div>;
}
handleNumOfPlayersChanged() {
console.log('do something');
}
}
export default SomeComponent;
index.spec.tsx:
import React from 'react';
import SomeComponent from './';
import { shallow } from 'enzyme';
import io from 'socket.io';
jest.mock('socket.io', () => {
const mSocket = {
on: jest.fn()
};
return jest.fn(() => mSocket);
});
describe('SomeComponent', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<SomeComponent></SomeComponent>);
jest.restoreAllMocks();
});
test('should mount component and register socket event', () => {
const instance = wrapper.instance() as any;
const mSocket = io();
expect(wrapper.text()).toBe('some component');
expect(mSocket.on).toBeCalledWith('numOfPlayersChanged', instance.handleNumOfPlayersChanged);
});
test('should handle player changed ', () => {
const logSpy = jest.spyOn(console, 'log');
const instance = wrapper.instance() as any;
instance.handleNumOfPlayersChanged();
expect(logSpy).toBeCalledWith('do something');
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58484558/index.spec.tsx
SomeComponent
✓ should mount component and register socket event (10ms)
✓ should handle player changed (7ms)
console.log node_modules/jest-mock/build/index.js:860
do something
-----------|----------|----------|----------|----------|-------------------|
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: 2 passed, 2 total
Snapshots: 0 total
Time: 3.62s, estimated 8s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58484558

Jest Enzyme How to update child props

I need to mutate React child component's prop which receives parents' function.
After updating the wrapper's function it changes but not affect to child component
// Component
import React from 'react';
export default class Component extends React.Component {
clickFunction() {
console.log("Parent's Click fn");
}
render() {
return (
<div>
<Button onClick={this.clickFunction} data-test-id="button" />
</div>
);
}
}
// Test
import React from 'react';
import {shallow} from 'enzyme';
const mockClickFunction = jest.fn(() => console.log('Mock Click fn'));
describe('Test Component', () => {
it('Should mutate child prop', () => {
const wrapper = shallow(<Component />);
wrapper.find('[data-test-id="button"]').simulate('click') // Parent's Click fn
console.log(wrapper.instance().clickFunction) // [Function: bound clickFunction]
wrapper.instance().clickFunction = mockClickFunction;
wrapper.update();
console.log(wrapper.instance().clickFunction) // [Function: mockConstructor]
wrapper.find('[data-test-id="button"]').simulate('click') // Parent's Click fn but should be Mock Click fn
})
})
How I can change child Component onClick function?
A reference to the original clickFunction has been used in the render. You can't mock it by overriding it after the component is already shallow-rendered.
Try spying on Component.prototype.clickFunction before calling shallow.
E.g.
index.jsx:
import React from 'react';
export default class Component extends React.Component {
clickFunction() {
console.log("Parent's Click fn");
}
render() {
return (
<div>
<button onClick={this.clickFunction} data-test-id="button" />
</div>
);
}
}
index.test.tsx:
import { shallow } from 'enzyme';
import React from 'react';
import Component from '.';
describe('55611882', () => {
it('should pass', () => {
const mockClickFunction = jest
.spyOn(Component.prototype, 'clickFunction')
.mockImplementation(() => console.log('Mock Click fn'));
const wrapper = shallow(<Component />);
wrapper.find('[data-test-id="button"]').simulate('click');
expect(mockClickFunction).toBeCalledTimes(1);
mockClickFunction.mockRestore();
});
});
test result:
PASS examples/55611882/index.test.jsx (13.099 s)
55611882
✓ should pass (81 ms)
console.log
Mock Click fn
at examples/55611882/index.test.jsx:9:41
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 75 | 100 | 50 | 75 |
index.jsx | 75 | 100 | 50 | 75 | 5
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.416 s

How to test a React ref with a callback?

Enzyme docs contains how to test a node having ref with wrapper.ref('nameOfRef'), but this only works for refs having just a string value like, if I have a node in React:
<span ref="secondRef" amount={4}>Second</span>
Then its test would be written like:
expect(wrapper.ref('secondRef').prop('amount')).to.equal(4);
But if I have a ref with a callback, then how to test it? Enzyme docs [1] does not says anything about this. For example, if I have a node with a ref like this:
<SomeCustomReactElement ref={_form => form = _form} />
Thanks for guidance.
[1]: http://airbnb.io/enzyme/docs/api/ReactWrapper/ref.html
You can call the ref callback manually using wrapper.getElement()['ref'](mockRef).
E.g.
index.tsx:
import React, { Component } from 'react';
export class SomeCustomReactElement extends Component {
doSomething() {
console.log('do somthing');
}
render() {
return <span>some custom react element</span>;
}
}
export default class MyComponent extends Component {
handleRef = (ref: SomeCustomReactElement) => {
console.log('handle ref');
ref.doSomething();
};
render() {
return (
<div>
<SomeCustomReactElement ref={this.handleRef} />
</div>
);
}
}
index.test.tsx:
import React from 'react';
import { shallow } from 'enzyme';
import MyComponent, { SomeCustomReactElement } from './';
describe('48349435', () => {
it('should handle ref', () => {
const wrapper = shallow(<MyComponent />);
const mRef = {
doSomething: jest.fn(),
};
wrapper.find(SomeCustomReactElement).getElement()['ref'](mRef);
expect(mRef.doSomething).toBeCalledTimes(1);
});
});
unit test result:
PASS examples/48349435/index.test.tsx (7.984 s)
48349435
✓ should handle ref (44 ms)
console.log
handle ref
at Object.MyComponent.handleRef [as ref] (examples/48349435/index.tsx:14:13)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 77.78 | 100 | 60 | 77.78 |
index.tsx | 77.78 | 100 | 60 | 77.78 | 5-8
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.273 s

Resources