dmikester1
dmikester1

Reputation: 1362

Testing a component where state variables are passed down from parent in React Testing Library

I'm extremely new to testing, but I know its necessary in a larger app. I am working on adding unit tests to my first component using React Testing Library. It is a comments component where the user can type in a comment, hit the button and it will display the comment along with any others above it in a div. If no comments exist, that div does not show. The comments component is taking in state props passed down from the parent.

Here is how I create the component in my testing (I'm not even sure if state is allowed/recommended in tests):

function EmptyComments() {
    const [comment, setComment] = useState('');
    const [comments, setComments] = useState([]);
    const saveComment = () => {
        const today = new Date();
        const newComment = {
            id: comment.length+1,
            comment: comment,
            date: today.toISOString(),
        };
        setComments(prevState => {
            return [...prevState, newComment]
        })
    }
    return <Comments
        comments={comments}
        commentMsg={''}
        setCommentMsg={mockedEmptyFn}
        comment={comment}
        setComment={setComment}
        saveComment={saveComment}
        deleteCommentAPI={mockedEmptyFn}
    />
}

Then my test that is failing:

test('Should display a newly typed comment', async() => {
        render(<EmptyComments />);
        const textArea = screen.getByPlaceholderText('Type new comment...');
        await userEvent.type(textArea, 'A new test comment!');
        const saveButton = screen.queryByRole('button', {
            name: /Save Comment/i
        });
        await userEvent.click(saveButton);
        await waitFor(() => {
            const commentsContainer = screen.findByTestId('comments');
            expect(commentsContainer).toBeInTheDocument();
        });
    });

Finally, I wanted to show a snippet from my comments component to show how the comments container is shown or hidden:

{comments && comments.length > 1 ? (
    <div className={'comments'} data-testid={'comments'}>

So I don't know if I need to get an updated view of the screen, or what I am doing wrong.

Upvotes: 0

Views: 8277

Answers (1)

Aerophite
Aerophite

Reputation: 215

You shouldn't need state within your tests. Plus, doing so just adds more code that might not be reliable and can't truly be tested either. My recommendation would be to do two tests.

  1. Check that when you input data and then hit the save button, that your saveComment function is called with the value of the input.
  2. Check that when you have comments sent in, they display.

Doing it this way guarantees that everything is called and displays as expected.

If you want to test it the way you have now, you would have to write the test for the Parent component so that all the state stuff is set automatically.


Something like this should work for you (I did not test this so some finagling might still have to be done)

const mockBaseProps = {
  comments: [],
  commentMsg: '',
  setCommentMsg: jest.fn(),
  comment: '',
  setComment: jest.fn(),
  saveComment: jest.fn(),
  deleteCommentAPI: jest.fn(),
};
const mockComment = 'A new test comment!';

// Creating a new component just so that the `mockBaseProps` get spread in every time and you don't have to always redefine them.
const Component = (props) => <Comments {...mockBaseProps} {...props} />;

test('can add a comment', async () => {
  render(<Component />);

  const textArea = screen.getByPlaceholderText('Type new comment...');
  await userEvent.type(textArea, mockComment);

  const saveButton = screen.queryByRole('button', { name: /Save Comment/i });
  await userEvent.click(saveButton);

  expect(mockBaseProps.saveComment).toHaveBeenCalledTimes(1);
  expect(mockBaseProps.saveComment).toHaveBeenCalledWith(mockComment);
});

test('comment displays', () => {
  render(
    <Component
      comments={[
        id: mockComment.length + 1,
        comment: mockComment,
        date: new Date().toISOString(),
      ]}
    />
  );

  await waitFor(() => expect(screen.getByTestId('comments')).toBeInTheDocument());

  /**
   * Note that `*ByTestId` is not generally recommended. It would be better to check that the actual comment text displays.
   * https://testing-library.com/docs/queries/about#priority
   *
   * Something like:
   * 
   * await waitFor(() => expect(screen.getByText(mockComment)).toBeInTheDocument());
   */
});

Upvotes: 1

Related Questions