Reputation: 452
I am trying to figure out the best way to unit test the following situation
App
holds the state of number of attendees
as well a method handleAttendeesChange()
to update the stateApp
renders a child component Attendees
and passes it 2 props: currentCount
and onValueChange
The question is: do I test that the state is changing properly in App.tsx or Attendees.tsx? I've seen examples where I should test the state change in the parent component but those examples show that the parent component displays the value in the DOM instead of the child.
Code is below
App.tsx
import React, { FC, Component } from 'react';
import {Attendees} from './Attendees';
interface AppState {
attendees: number
}
export default class App extends Component<{}, AppState> {
constructor(props:any) {
super(props);
this.handleAttendeesChange = this.handleAttendeesChange.bind(this);
this.state = {
attendees: 0
};
}
handleAttendeesChange(value: number) {
this.setState({ attendees: this.state.attendees + value});
}
render() {
return (
<div>
<h1>Parent Component</h1>
<Attendees currentCount={this.state.attendees} onValueChange={this.handleAttendeesChange} />
</div>
)
}
}
Attendees.tsx
import React, { FC } from 'react';
type AttendeesProps = {
currentCount: number,
onValueChange: (value: number) => void
}
export const Attendees:FC<AttendeesProps> = ({ currentCount, onValueChange }) => {
return (
<div>
<button data-testid="countUp" onClick={() => onValueChange(1)}>
Up
</button >
<button data-testid="countDown" onClick={() => onValueChange(-1)}>
Down
</button >
<p data-testid="currentCount">
{currentCount}
</p>
</div>
)
}
Here's what I am currently testing in Attendeees.test.tsx using react-testing-library
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { unmountComponentAtNode } from "react-dom";
// import { act } from "react-dom/test-utils";
import {Attendees} from './Attendees';
let container: any = null;
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("Main");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("Attendees should respond to callback props", () => {
const onValueChange = jest.fn();
const { getByTestId } = render(<Attendees currentCount={0} onValueChange={onValueChange} />, container)
fireEvent.click(getByTestId('countUp'))
expect(onValueChange).toBeCalledWith(1);
expect(onValueChange).toHaveBeenCalledTimes(1);
expect(getByTestId('currentCount').textContent).toBe('1');
})
Upvotes: 2
Views: 8281
Reputation: 4366
Late answer, but may help someone having same question.
"Do I test that the state is changing properly in App.tsx or Attendees.tsx?"
We don't have to test whether setState
is working properly or not. However, if we want to test that the updated count is displayed in the Attendees component, we could test it in App.test.tsx
, because that is where the state resides.
We don't have to create a container in beforeEach
and then remove it in afterEach
. The react-testing-library
takes care of it.
Here's my version of the Attendees.test.tsx:
// Attendees.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Attendees } from './Attendees';
describe('Attendees component', () => {
it('should respond to Up button click', () => {
const onValueChange = jest.fn();
render(<Attendees currentCount={0} onValueChange={onValueChange} />);
// Don't have to use data-testid all the time. Here's another way
fireEvent.click(screen.getByText('up', { exact: false }));
expect(onValueChange).toBeCalledWith(1);
expect(onValueChange).toHaveBeenCalledTimes(1);
});
it('should respond to Down button click', () => {
const onValueChange = jest.fn();
render(<Attendees currentCount={0} onValueChange={onValueChange} />);
fireEvent.click(screen.getByText('down', { exact: false }));
expect(onValueChange).toBeCalledWith(-1);
expect(onValueChange).toHaveBeenCalledTimes(1);
});
});
I would place the test that checks for the current count display in the App.test.tsx:
// App.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';
describe('App component', () => {
it('should display increased attendee count when the Up button is clicked', () => {
render(<App />);
// Sometimes it may be a good idea to ensure that the precondition is true
expect(screen.getByLabelText('current-attendee-count').textContent).toBe('0');
fireEvent.click(screen.getByText('up', { exact: false }));
expect(screen.getByLabelText('current-attendee-count').textContent).toBe('1');
});
});
The examples above were tested using React v17.0.1, and @testing-library/react v11.2.3.
Upvotes: 4