I learning to write unit tests in React. The test should check if an onChange function is called when there is a change in the input field. This is my simple search bar component:
import {Input} from './Input';
const SearchBar = (props) => {
return (
<Input
type="search"
data-test="search"
onChange={e => props.onSearch(e.target.value)} />
);
};
export default SearchBar;
The test I wrote is supposed to simulate a change on input field, what in turn should invoke a function call.
describe('Search', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('Should call onSearch function', () => {
const wrapper = mount(<SearchBar onSearch={onSearch}/>);
const searchBar = wrapper.find('[data-test=search]').at(0);
searchBar.simulate('change', {target: { 'test' }});
expect(onSearch).toBeCalledTimes(1);
});
});
Here, instead of being called 1 time, the function is not called at all. I cannot figure out why. Can you explain where am I making a mistake?
You should create a mocked onSearch using jest.fn(). The CSS selector should be '[data-test="search"]'.
E.g.
SearchBar.tsx:
import React from 'react';
function Input(props) {
return <input {...props} />;
}
const SearchBar = (props) => {
return <Input type="search" data-test="search" onChange={(e) => props.onSearch(e.target.value)} />;
};
export default SearchBar;
SearchBar.test.tsx:
import { mount } from 'enzyme';
import React from 'react';
import SearchBar from './SearchBar';
describe('Search', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('Should call onSearch function', () => {
const onSearch = jest.fn();
const wrapper = mount(<SearchBar onSearch={onSearch} />);
const searchBar = wrapper.find('[data-test="search"]').at(0);
searchBar.simulate('change', { target: { value: 'test' } });
expect(onSearch).toBeCalledTimes(1);
});
});
test result:
PASS examples/68669299/SearchBar.test.tsx (7.33 s)
Search
✓ Should call onSearch function (47 ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
SearchBar.tsx | 100 | 100 | 100 | 100 |
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.002 s, estimated 9 s
Related
Here I need to add tests for handleBeforeUnload in window eventListener but I am getting error, How will I resolve it?
expect(jest.fn())[.not].toHaveBeenCalled()
Matcher error: received value must be a mock or spy function
Received has value: undefined
map.beforeunload();
expect(wrapper.handleBeforeUnload).toHaveBeenCalled();
My component
componentDidMount() {
window.addEventListener('beforeunload', (event) => {
this.handleBeforeUnload();
});
}
handleBeforeUnload () {
}
My test spec:
it('should call handleBeforeUnload', () => {
const historyMock = { listen: jest.fn(), replace: jest.fn() };
const map = {};
window.addEventListener = jest.fn((event, cb) => {
map[event] = cb;
});
const wrapper = shallow(
<AppRoutes history={historyMock} getDctkConfig={getDctkConfig} />,
);
map.beforeunload();
expect(wrapper.handleBeforeUnload).toHaveBeenCalled();
});
You didn't spy the.handleBeforeUnload() method of the component class. You can spy it through Component.prototype.handleBeforeUnload. Besides, you can mock the implementation of the .addEventListener() method and invoke the listener function manually.
index.tsx:
import React, { Component } from 'react';
export default class AppRoutes extends Component {
componentDidMount() {
window.addEventListener('beforeunload', (event) => {
this.handleBeforeUnload();
});
}
handleBeforeUnload() {}
render() {
return <div>app routes</div>;
}
}
index.test.tsx:
import { shallow } from 'enzyme';
import React from 'react';
import AppRoutes from './';
describe('69346085', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should pass', () => {
const handleBeforeUnloadSpy = jest.spyOn(AppRoutes.prototype, 'handleBeforeUnload');
jest
.spyOn(window, 'addEventListener')
.mockImplementation((type: string, listener: EventListenerOrEventListenerObject) => {
typeof listener === 'function' && listener({} as Event);
});
shallow(<AppRoutes />);
expect(handleBeforeUnloadSpy).toHaveBeenCalled();
});
});
test result:
PASS examples/69346085/index.test.tsx (9.191 s)
69346085
✓ should pass (7 ms)
-----------|---------|----------|---------|---------|-------------------
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.814 s, estimated 10 s
I'm trying to test searchForm component of React with onChange prop.
const SearchForm = () => {
const [value, setValue] = useState('');
return (
<form className={styles.searchForm}>
<input
value={value}
onChange={(e) => setValue(e.target.value)} // test this line
className={styles.searchForm__input}
/>
<button type="submit" aria-label="Search" className={styles.searchForm__button} />
</form>
);
};
Here is example of my test:
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
import SearchForm from '../index';
const setUp = () => {
const renderer = new ShallowRenderer();
renderer.render(<SearchForm />);
return renderer.getRenderOutput();
};
describe('render form component', () => {
it('handle onChange in form input field', () => {
const result = setUp();
expect(result).toMatchSnapshot();
});
});
This test passes, but JEST says that this line of code (with onChange) is uncovered.
I found how to launch onChange:
result.props.children[0].props.onChange();
This launches original prop but i get error on e.target -- cannot read property of undefined.
I feel like i need to mock setValue somehow, but i can't figure out how. I'm new to JEST.
Maybe this may be done with just react-test-renderer in better way.
Here is the solution:
index.tsx:
import React, { useState } from 'react';
export const SearchForm = () => {
const [value, setValue] = useState('');
return (
<form>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<button type="submit" aria-label="Search" />
</form>
);
};
index.test.tsx:
import React from 'react';
import TestRenderer, { act } from 'react-test-renderer';
import ShallowRenderer from 'react-test-renderer/shallow';
import { SearchForm } from './';
describe('66907704', () => {
it('should handle onChange event', () => {
const testRenderer = TestRenderer.create(<SearchForm />);
const testInstance = testRenderer.root;
expect(testInstance.findByType('input').props.value).toEqual('');
const mEvent = { target: { value: 'teresa teng' } };
act(() => {
testInstance.findByType('input').props.onChange(mEvent);
});
expect(testInstance.findByType('input').props.value).toEqual('teresa teng');
});
it('should handle onChange event when use shallow render', () => {
const shallowRenderer = ShallowRenderer.createRenderer();
shallowRenderer.render(<SearchForm />);
let tree = shallowRenderer.getRenderOutput();
let input = tree.props.children[0];
const mEvent = { target: { value: 'teresa teng' } };
input.props.onChange(mEvent);
tree = shallowRenderer.getRenderOutput();
input = tree.props.children[0];
expect(input.props.value).toEqual('teresa teng');
});
});
unit test result:
PASS examples/66907704/index.test.tsx (6.636 s)
66907704
✓ should handle onChange event (10 ms)
✓ should handle onChange event when use shallow render (1 ms)
-----------|---------|----------|---------|---------|-------------------
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: 7.443 s
package versions:
"jest": "^26.6.3",
"react": "^16.14.0",
Given the following component:
import * as React from "react";
import "./styles.css";
export default function App() {
const scrollContainerRef = React.useRef<HTMLDivElement | null>(null);
const handleClick = () => {
scrollContainerRef?.current?.scrollBy({ top: 0, left: 100 });
};
return (
<div aria-label="wrapper" ref={scrollContainerRef}>
<button onClick={handleClick}>click</button>
</div>
);
}
How do I write a test using Jest and React Testing library to check that when the button is clicked, scrollBy is triggered on the wrapper?
I have tried the following and it doesn't seem to be working:
test('Clicking on button should trigger scroll',() => {
const myMock = jest.fn();
Object.defineProperty(HTMLElement.prototype, 'scrollBy', {
configurable: true,
value: myMock(),
})
render(<MyComponent />)
fireEvent.click(screen.getByText(/click/i))
expect(myMock).toHaveBeenCalledWith({top: 0, left: 100})
})
Since jsdom does NOT implements Element.scrollBy() method, see PR. We can create a mocked ref object with getter and setter to intercept React's assignment process to ref.current, and install spy or add mock in the process.
E.g.
App.tsx:
import * as React from 'react';
export default function App() {
const scrollContainerRef = React.useRef<HTMLDivElement | null>(null);
const handleClick = () => {
scrollContainerRef?.current?.scrollBy({ top: 0, left: 100 });
};
return (
<div aria-label="wrapper" ref={scrollContainerRef}>
<button onClick={handleClick}>click</button>
</div>
);
}
App.test.tsx:
import React, { useRef } from 'react';
import { render, screen, fireEvent } from '#testing-library/react';
import { mocked } from 'ts-jest/utils';
import App from './App';
jest.mock('react', () => {
return {
...jest.requireActual<typeof React>('react'),
useRef: jest.fn(),
};
});
const useMockRef = mocked(useRef);
describe('63702104', () => {
afterAll(() => {
jest.resetAllMocks();
});
test('should pass', () => {
const ref = { current: {} };
const mScrollBy = jest.fn();
Object.defineProperty(ref, 'current', {
set(_current) {
if (_current) {
_current.scrollBy = mScrollBy;
}
this._current = _current;
},
get() {
return this._current;
},
});
useMockRef.mockReturnValueOnce(ref);
render(<App />);
fireEvent.click(screen.getByText(/click/i));
expect(mScrollBy).toBeCalledWith({ top: 0, left: 100 });
});
});
test result:
PASS examples/63702104/App.test.tsx (9.436 s)
63702104
✓ should pass (33 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 75 | 100 | 100 |
App.tsx | 100 | 75 | 100 | 100 | 7
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.21 s
I wrote a test case to call onBlur method, but I'm getting an error when I try to assert it. Here is the above test case.
it("call the handlingBlurEmail method", () => {
const wrapper = mount(
<App childRef={() => {}} />
);
const comp = wrapper.find({ id: "email" }).first();
comp.prop("onBlur")({
target: { id: "email", value: "test#gmail.com" }
});
expect(
wrapper
.find("AccountForm")
.state()
.onBlur()
).toHaveBeenCalled();
});
and the function for which I'm writing test case is
mailReference = React.createRef();
handlingEmailBlur = events => {
this.mailReference.current.validate(events.target.value);
};
render = () {
......
return (
<div className="Form1">
onBlur={this.handlingEmailBlur}
</div>
)
.....
}
Please let me know how to add assert statement in order to call the onBlur() method in the above test case
Here is the unit testing solution:
index.tsx:
import React, { Component } from 'react';
class App extends Component {
mailReference = React.createRef();
handlingEmailBlur = (events) => {
this.mailReference.current.validate(events.target.value);
};
render() {
return (
<div className="Form1" onBlur={this.handlingEmailBlur}>
some component
</div>
);
}
}
export default App;
index.spec.tsx:
import App from './index';
import { mount } from 'enzyme';
import React from 'react';
describe('59455504', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('call the handlingBlurEmail method', () => {
const mailReference = { current: { validate: jest.fn() } };
jest.spyOn(React, 'createRef').mockReturnValue(mailReference);
const wrapper = mount(<App childRef={() => {}} />);
const mEvent = {
target: { id: 'email', value: 'test#gmail.com' },
};
wrapper.find('.Form1').prop('onBlur')(mEvent);
expect(mailReference.current.validate).toBeCalledWith(mEvent.target.value);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59455504/index.spec.jsx (8.328s)
59455504
✓ call the handlingBlurEmail method (40ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.jsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 9.769s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59455504
Okay i've uploaded an even more straight forward example. I just can't get mock functions to be called when they are located inside of a helper function. Not sure what i'm doing wrong.
class TodoSearch extends Component {
handleSearch = () => {
const searchInput = this.searchInput.value;
// passed down from parent component
this.props.onSearch(searchInput);
};
handleCheckbox = e => {
const showCompleted = e.target.checked;
// passed down from parent component
this.props.onCheckbox(showCompleted);
};
render() {
return (
<form>
<FormGroup>
<FormControl
type="search"
className="todoSearchInput"
placeholder="Search"
inputRef={input => (this.searchInput = input)}
onChange={this.handleSearch}
/>
<Checkbox checked={this.props.checked} onChange={this.handleCheckbox}>
Show completed todos
</Checkbox>
</FormGroup>
</form>
);
}
}
export default TodoSearch;
So i'm using Jest and Enzyme as thus, the 'shallow'. This is what i've successful done so far
import React from 'react';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
import TodoSearch from './TodoSearch';
describe(TodoSearch, () => {
const searchText = 'Buy Milk';
const mockOnSearch = jest.fn();
const component = shallow(<TodoSearch onSearch={mockOnSearch} />);
it('should exist', () => {
const component = renderer.create(<TodoSearch />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('contains the form', () => {
expect(component.find('form')).toHaveLength(1);
expect(component.find('Checkbox')).toHaveLength(1);
expect(component.find('.todoSearchInput')).toHaveLength(1);
});
});
How can i test that onSearch inside the handleSearch is run? Tried a bunch of ways but just not getting it. Any help greatly appreciated.
Here is the solution, I simplify your component, remove the unrelated part.
dependencies:
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"jest": "^24.8.0",
"react": "^16.9.0",
TodoSearch.tsx:
import React, { Component } from 'react';
import console = require('console');
interface ITodoSearchProps {
onSearch(input): any;
onCheckbox(value): any;
}
class TodoSearch extends Component<ITodoSearchProps> {
private searchInput: HTMLInputElement | null = null;
constructor(props: ITodoSearchProps) {
super(props);
}
public handleSearch = () => {
if (this.searchInput) {
const searchInput = this.searchInput.value;
console.log('searchInput: ', searchInput);
// passed down from parent component
this.props.onSearch(searchInput);
}
}
public handleCheckbox = e => {
const showCompleted = e.target.checked;
// passed down from parent component
this.props.onCheckbox(showCompleted);
}
public render() {
return (
<form>
<input
type="search"
className="todoSearchInput"
placeholder="Search"
onChange={this.handleSearch}
ref={input => (this.searchInput = input)}
/>
</form>
);
}
}
export default TodoSearch;
TodoSearch.spec.tsx:
import React from 'react';
import TodoSearch from './TodoSearch';
import { mount } from 'enzyme';
describe('TodoSearch', () => {
const mockOnSearch = jest.fn();
const mockOnCheckbox = jest.fn();
it('should handle search correctly', () => {
const searchInput = 'jest';
const wrapper = mount(<TodoSearch onSearch={mockOnSearch} onCheckbox={mockOnCheckbox}></TodoSearch>);
(wrapper.find('input').instance() as any).value = searchInput;
wrapper.find('input').simulate('change');
expect(mockOnSearch).toBeCalledWith(searchInput);
});
});
Unit test result with coverage report:
PASS src/stackoverflow/47493126/TodoSearch.spec.tsx
TodoSearch
✓ should handle search correctly (50ms)
searchInput: jest
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 90 | 75 | 83.33 | 88.89 | |
TodoSearch.tsx | 90 | 75 | 83.33 | 88.89 | 24,26 |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.846s, estimated 6s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47493126