write unit test for event handlers that updates some state in react using jest and enzyme - reactjs

I am trying to write some unit tests for the event handlers that I wrote inside my component. I would like to write tests for the states updates inside that event handlers.
For example I have the following function that are called onMouseDown inside the component. How can I write some tests about that.
const [visible, setVisibility ] = useState(false);
const onSelection = () => {
setVisibility(!visible)
};
<div onMouseDown ={()=> onSelection(items)}>Click</div>
{visible && <div>simple text</div>}
Can anybody guide me through there. Thanks in advance

Suppose the component like this:
index.tsx:
import React, { useState } from 'react';
export default function Example() {
const [visible, setVisibility] = useState(false);
const onSelection = () => {
setVisibility(!visible);
};
return (
<div>
<div onMouseDown={() => onSelection()}>Click</div>
{visible && <div>simple text</div>}
</div>
);
}
We test the behavior of the component from the user's perspective.
We should test: What is the component rendering before triggering the mousedown event and what is rendered after the visible state changes.
index.test.tsx
import { shallow } from 'enzyme';
import React from 'react';
import Example from './';
describe('70577146', () => {
test('should pass', () => {
const wrapper = shallow(<Example />);
const button = wrapper.find('div[children="Click"]');
expect(wrapper.find('div[children="simple text"]').exists()).toBeFalsy();
button.simulate('mousedown');
expect(wrapper.find('div[children="simple text"]').exists()).toBeTruthy();
});
});
Test result:
PASS examples/70577146/index.test.tsx (11.966 s)
70577146
✓ should pass (11 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: 13.599 s, estimated 14 s

Related

React testing lib not update the state

My component:
import React from 'react'
const TestAsync = () => {
const [counter, setCounter] = React.useState(0)
const delayCount = () => (
setTimeout(() => {
setCounter(counter + 1)
}, 500)
)
return (
<>
<h1 data-testid="counter">{ counter }</h1>
<button data-testid="button-up" onClick={delayCount}> Up</button>
<button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
</>
)
}
export default TestAsync
My test file:
describe("Test async", () => {
it("increments counter after 0.5s", async () => {
const { getByTestId, getByText } = render(<TestAsync />);
fireEvent.click(getByTestId("button-up"));
const counter = await waitForElement(() => getByTestId("counter"));
expect(counter).toHaveTextContent("1");
});
});
After run the test file, I got error said:
Expected element to have text content:
1
Received:
0
I am a little bit confused why I use waitForElement to get the element but why the element still has the old value?
React-testing-lib version 9.3.2
First of all, waitForElement has been deprecated. Use a find* query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use waitFor instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor
Now, we use waitFor:
waitFor may run the callback a number of times until the timeout is reached.
You need to wrap the assertion statement inside the callback of the waitFor. So that waitFor can run the callback multiple times. If you put the expect(counter).toHaveTextContent('1'); statement outside and after waitFor statement, then it only run once. React has not been updated when assertions run.
Why RTL will run the callback multiple times(run callback every interval before timeout)?
RTL use MutationObserver to watch for changes being made to the DOM tree, see here. Remember, our test environment is jsdom, it supports MutationObserver, see here.
That means when React updates the state and applies the update to the DOM, the changes of the DOM tree can be detected and RTL will run the callback again including the assertion. When the React component states are applied and become stable, the last run of the callback is taken as the final assertion of the test. If the assertion fails, an error is reported, otherwise, the test passes.
So the working example should be:
index.tsx:
import React from 'react';
const TestAsync = () => {
const [counter, setCounter] = React.useState(0);
const delayCount = () =>
setTimeout(() => {
setCounter(counter + 1);
}, 500);
return (
<>
<h1 data-testid="counter">{counter}</h1>
<button data-testid="button-up" onClick={delayCount}>
Up
</button>
<button data-testid="button-down" onClick={() => setCounter(counter - 1)}>
Down
</button>
</>
);
};
export default TestAsync;
index.test.tsx:
import { fireEvent, render, waitFor } from '#testing-library/react';
import React from 'react';
import TestAsync from '.';
import '#testing-library/jest-dom/extend-expect';
describe('Test async', () => {
it('increments counter after 0.5s', async () => {
const { getByTestId } = render(<TestAsync />);
fireEvent.click(getByTestId('button-up'));
await waitFor(() => {
const counter = getByTestId('counter');
expect(counter).toHaveTextContent('1');
});
});
});
Test result:
PASS stackoverflow/71639088/index.test.tsx
Test async
✓ increments counter after 0.5s (540 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 88.89 | 100 | 75 | 88.89 |
index.tsx | 88.89 | 100 | 75 | 88.89 | 17
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.307 s

update and get new state from setState Hook with Jest

I succeed to create a functional component, to mock the useState function and to get the call to the mocking function. But the functional component keep its initial value. Is there no way at all to get the new functional component created after update with its new initial "useState" value ?
For example, if I do an "simulate(click)" with enzyme twice on the button in the code below, I will have twice the value "1" returned in the mock function.
This limits a lot possible tests.
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>Vous avez cliqué {count} fois</p>
<button id="count-up" onClick={() => setCount(count + 1)}>
Cliquez ici
</button>
</div>
);
}
here's my test code :
import React, { useState as useStateMock, setState } from 'react';
import { shallow, mount, render } from 'enzyme';
import Example from './file with example component'
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
describe('<Home />', () => {
let wrapper;
const setState = jest.fn();
beforeEach(async () => {
useStateMock.mockImplementation(init => [init, setState]);
wrapper = mount(<Example />)
});
describe('Count Up', () => {
it('calls setCount with count + 1', () => {
wrapper.find('#count-up').first().simulate('click');
expect(setState).toHaveBeenLastCalledWith(1);
wrapper.find('#count-up').simulate('click');
expect(setState).toHaveBeenLastCalledWith(2);
});
});
})
I would like the setState mock function to return 1 then 2 as its corresponds to the initial state 0 + 1 and then this results 1 + 1 again.
But the functional component is not updated, and I don't know how to do that.
Thanks for your help
Because you mock useState without providing an implementation, the functionality of useState has changed.
Don't mock the react module and its implementations. Continue to use their original implementation. You should test the component behavior instead of the implementation detail.
In other words, we want to test from the user's point of view, they don't care about your implementation, they just care about what they can see on the screen.
Component behavior is: What does your component render when the state changes.
So the unit test should be:
Example.tsx:
import React from 'react';
import { useState } from 'react';
export function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>Vous avez cliqué {count} fois</p>
<button id="count-up" onClick={() => setCount(count + 1)}>
Cliquez ici
</button>
</div>
);
}
Example.test.tsx:
import { mount } from 'enzyme';
import React from 'react';
import { Example } from './Example';
describe('70585877', () => {
test('should pass', () => {
const wrapper = mount(<Example />);
const button = wrapper.find('#count-up');
// The initial state of the component as seen by the user
expect(wrapper.find('p').text()).toEqual('Vous avez cliqué 0 fois');
// User click the button
button.simulate('click');
// The next state of the component as seen by the user
expect(wrapper.find('p').text()).toEqual('Vous avez cliqué 1 fois');
// User click the button again
button.simulate('click');
// The next state of the component as seen by the user
expect(wrapper.find('p').text()).toEqual('Vous avez cliqué 2 fois');
});
});
Test result:
PASS examples/70585877/Example.test.tsx (9.532 s)
70585877
✓ should pass (34 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
Example.tsx | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.519 s

How can I test inner text of a div element

I'm trying to find the best practice to test textContent for a div element here, using react testing library.
Say I'd like to test this simple react component to see if {props.text} is rendered properly on the HTML DOM.
const Simple = props => (
<>
<div> {props.text} </div>
<div> test text </div>
</>
);
I've tried to use getByText, then test expect(getByText('text passed as prop')).toBeDefined() but it didn't seem to work properly.
It must be much easier if I add a className or id for the first <div /> then probably I can just go for querySelector, but what if I didn't want to add any HTML attribute here? How can I locate this element properly?
Is there any solution to find the first element that has no attribute and test its inner text?
It works for me using "#testing-library/react": "^9.4.0". E.g.
index.tsx:
import React from 'react';
const Simple = (props) => (
<>
<div> {props.text} </div>
<div> test text </div>
</>
);
export { Simple };
index.test.tsx:
import React from 'react';
import { Simple } from './';
import { render } from '#testing-library/react';
describe('60534908', () => {
it('should find div element', () => {
const mProps = { text: 'text passed as prop' };
const { getByText } = render(<Simple {...mProps} />);
expect(getByText('text passed as prop')).toBeDefined();
expect(getByText('test text')).toBeDefined();
});
});
Unit test results with 100% coverage:
PASS stackoverflow/60534908/index.test.tsx (8.606s)
60534908
✓ should find div element (37ms)
-----------|---------|----------|---------|---------|-------------------
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.784s, estimated 11s

How to use .contains(nodeOrNodes) API when the contained react element has an arrow function event handler?

For below example, .contains(nodeOrNodes) => Boolean API works fine.
index.tsx:
import React from 'react';
const Comp = ({ onChange }) => (
<form>
<input type="text" placeholder="username" onChange={onChange} />
</form>
);
export default Comp;
index.test.tsx:
import React from 'react';
import { shallow } from 'enzyme';
import Comp from '.';
describe('Comp', () => {
it('should render', () => {
const noop = () => null;
const wrapper = shallow(<Comp onChange={noop} />);
expect(
wrapper.contains(
<form>
<input type="text" placeholder="username" onChange={noop} />
</form>,
),
).toBeTruthy();
});
});
Unit test results:
PASS src/stackoverflow/46133847/02/index.test.tsx
Comp
✓ should render (13ms)
-----------|----------|----------|----------|----------|-------------------|
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: 7.754s, estimated 24s
But, if I change the onChange event handler using the arrow function:
index.ts:
import React from 'react';
const Comp = ({ onChange }) => (
<form>
<input type="text" placeholder="username" onChange={(e) => onChange(e)} />
</form>
);
export default Comp;
The unit test will fail.
FAIL src/stackoverflow/46133847/02/index.test.tsx
Comp
✕ should render (18ms)
● Comp › should render
expect(received).toBeTruthy()
Received: false
13 | </form>,
14 | ),
> 15 | ).toBeTruthy();
| ^
16 | });
17 | });
18 |
at Object.it (src/stackoverflow/46133847/02/index.test.tsx:15:7)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 7.689s, estimated 25s
I think the test failed because the arrow function created a new function reference. This new function has a different reference with noop function passed into Comp.
But what I want is, is there any way like expect.any(Function) of jestjs, just to assert whether or not the wrapper contains any function of onChange event handler?
Package versions:
"enzyme": "^3.10.0",
"jest": "^24.9.0",
To be honest, I do not think using .contains(nodeOrNodes) is good way for this situation.
My personal suggestion is followed by below:
let inputComponent = wrapper.find("input[placeholder]='username'");
expect(inputComponent.length).toBe(1); // This tests your component is exists rather than contains)
expect(inputComponent.props().onChange).toEqual(noop); //This tests onChange function is equal your mock function
Please vote it, if it works for you

Not getting expected result from .toHaveBeenCalledTimes() in react-testing-library

Anyhow, trying to test if a function has been called after its fired. The fireEvent is working as I get a console.log from that function. But the .toHaveBeenCalledTimes(1) returns 0. What have i missed?
If I have the handleLoginSubmit function in the parent and pass it as a prop down to the child and in the test everything passes. But if it's in the same component it fails. Using typescript if that has any meaning.
This is tested
import React, { FC } from 'react';
type Event = React.FormEvent<HTMLFormElement>;
interface Login {
handleLoginSubmit?: (event: Event) => React.ReactNode;
}
const Login: FC<Login> = () => {
const handleLoginSubmit = (_event: Event) => {
console.log('Firing' ); // This is logged
};
return (
<form data-testid='form' onSubmit={(event) => handleLoginSubmit(event)}>
<input data-testid='email'/>
<input data-testid='password'/>
<button data-testid='login-button'>login</button>
</form>
);
};
export default Login;
My test for submiting
it('should handle ClickEvents', () => {
const handleLoginSubmit = jest.fn();
const { getByTestId } = render(<Login/>);
expect(getByTestId('login-button')).toBeTruthy();
fireEvent.submit(getByTestId('form'));
expect(handleLoginSubmit).toHaveBeenCalledTimes(1);
});
Error message
● Login page › should handle ClickEvents
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
32 | fireEvent.submit(getByTestId('form'));
33 |
> 34 | expect(handleLoginSubmit).toHaveBeenCalledTimes(1);
| ^
35 |
36 | });
37 | });
at Object.it (__tests__/components/Login.test.tsx:34:31)
You can't assert if the handleLoginSubmit function is to be called directly. Since it's defined in the private scope of Login SFC. You can't mock or spy on this function because you can't access it. So, you need to test it indirectly. Since you are using console.log in this function, we can spy console.log. If it's been called, that means the handleLoginSubmit function has been called.
E.g.
index.tsx:
import React, { FC } from "react";
type Event = React.FormEvent<HTMLFormElement>;
interface Login {
handleLoginSubmit?: (event: Event) => React.ReactNode;
}
const Login: FC<Login> = () => {
const handleLoginSubmit = (_event: Event) => {
console.log("Firing");
};
return (
<form data-testid="form" onSubmit={event => handleLoginSubmit(event)}>
<input data-testid="email" />
<input data-testid="password" />
<button data-testid="login-button">login</button>
</form>
);
};
export default Login;
index.spec.tsx:
import { render, fireEvent } from "#testing-library/react";
import Login from "./";
import React from "react";
it("should handle ClickEvents", () => {
const logSpy = jest.spyOn(console, "log");
const { getByTestId } = render(<Login />);
expect(getByTestId("login-button")).toBeTruthy();
fireEvent.submit(getByTestId("form"));
expect(logSpy).toHaveBeenCalledTimes(1);
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59162138/index.spec.tsx
✓ should handle ClickEvents (42ms)
console.log node_modules/jest-mock/build/index.js:860
Firing
-----------|----------|----------|----------|----------|-------------------|
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: 3.987s, estimated 9s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59162138

Resources