How to test a React ref with a callback? - reactjs

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

Related

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

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"

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

How to fix 'Expected mock function to have been called, but it was not called' in Jest

I want to test that a class method is called on 'mousedown' event. Please take a look on the code below.
This is my component:
import React, { Component } from 'react';
import styled from 'styled-components';
import MyChildClass from './MyChildClass';
export default class MyClass extends Component {
constructor(props) {
super(props);
// Init state
this.state = {
balls: []
};
this.onMouseUpHandler = this.onMouseUpHandler.bind(this, this.state.balls);
}
onMouseDownHandler = (balls, e) => {
...
};
render() {
return (
<StyledEnvironment className='wrapper'>
<div
onMouseDown={this.onMouseDownHandler}
onMouseUp={this.onMouseUpHandler}
>
{balls}
</div>
</StyledEnvironment>
);
}
And this is my test:
import React from 'react';
import { mount, shallow } from 'enzyme';
import MyClass from '../MyClass';
it('should call onMouseDownHandler on mouse down', () => {
//...arrange
const instance = component.instance();
const wrapper = component.find('.wrapper').at(0);
instance.setMousePosition = jest.fn();
instance.onMouseDownHandler = jest.fn();
instance.forceUpdate();
component.update();
//...act
wrapper.simulate('mouseDown');
//...assert
expect(instance.onMouseDownHandler).toHaveBeenCalled();
});
So, I eventually expect the test to pass, but still the runner returns fail, with message
Expected mock function to have been called, but it was not called.
I've gone through many examples on Google, and found out that this is proper pattern of testing events.
Use arrow function as the method of class is not easy to test. Because it will be used as property of class, not instance method of class. So I suggest you refactor the method.
After refactoring, you can use jest.spyOn(object, methodName) to spy on the onMouseDownHandler method.
For example,
index.tsx:
import React, { Component } from 'react';
const StyledEnvironment = ({ children, className }) => <div className={className}>{children}</div>;
export default class MyClass extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
balls: []
};
}
onMouseDownHandler(balls, e) {
// TBD
}
onMouseUpHandler(balls, e) {
// TBD
}
render() {
return (
<StyledEnvironment className="wrapper">
<div
onMouseDown={e => this.onMouseDownHandler(this.state.balls, e)}
onMouseUp={e => this.onMouseUpHandler(this.state.balls, e)}>
{this.state.balls}
</div>
</StyledEnvironment>
);
}
}
index.spec.tsx:
import React from 'react';
import { shallow } from 'enzyme';
import MyClass from './';
describe('MyClass', () => {
test('should handle mousedown event', () => {
const wrapper = shallow(<MyClass></MyClass>);
const onMouseDownHandlerSpy = jest.spyOn(MyClass.prototype, 'onMouseDownHandler');
wrapper.find('div').simulate('mouseDown');
expect(onMouseDownHandlerSpy).toBeCalled();
});
});
Unit test result with coverage report:
PASS src/stackoverflow/58652312/index.spec.tsx
MyClass
✓ should handle mousedown event (12ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 76.47 | 100 | 62.5 | 91.67 | |
index.tsx | 76.47 | 100 | 62.5 | 91.67 | 25 |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.951s, estimated 9s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58652312

Creating test cases in Jest for render

I have the following code to test in React
render() {
if (this.state.isDone) {
return(...)
} else {
return(...)
}
}
In the code above, I need to test both conditions. However, when running the below test, one branch is getting tested.
it('renderTest', () => {
const wrapper = shallow(<CheckState />);
expect(wrapper.exists()).toBe(true);
});
In the above code, only the else part gets covered in the test. The parameter in this is assigned during the process of the component. Is it possible for me to test the same by passing a parameter?
You can use setState method of enzyme to change your component state. Below solution only for testing the render method independently without simulate an event.
index.tsx:
import React from 'react';
interface ICheckStateState {
isDone: boolean;
}
export class CheckState extends React.Component<{}, ICheckStateState> {
constructor(props) {
super(props);
this.state = {
isDone: false
};
}
public render() {
if (this.state.isDone) {
return <div>Done</div>;
} else {
return <div>Not Done</div>;
}
}
}
index.spec.tsx:
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { CheckState } from './';
describe('CheckState', () => {
describe('#render', () => {
let wrapper: ShallowWrapper;
beforeEach(() => {
wrapper = shallow(<CheckState></CheckState>);
});
it('should render correctly', () => {
expect(wrapper.exists()).toBe(true);
expect(wrapper.text()).toBe('Not Done');
wrapper.setState({ isDone: true });
expect(wrapper.text()).toBe('Done');
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58059957/index.spec.tsx
CheckState
#render
✓ should render correctly (8ms)
-----------|----------|----------|----------|----------|-------------------|
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: 2.819s, estimated 6s
Yes we can do that if we modify the state inside the component to accommodate a specific value from props and if nothing is passed from prop then give a default value, something like this -:
class CheckState extends Component {
constructor(props){
this.state = {
isDone: props.isDone || false
};
}
.......
Also test case needs to be something like this -:
it('renderTest', () => {
const wrapper = shallow(<CheckState />);
expect(wrapper.exists()).toBe(true);
const wrapper = shallow(<CheckState isDone={true}/>);
expect(wrapper.exists()).toBe(true);
});

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

Resources