1. 30
    Testing Components with jest-dom's toHaveClass Matcher
    2m 37s

Testing Components with jest-dom's toHaveClass Matcher

InstructorJamund Ferguson

Share this video with your friends

Send Tweet

Jest DOM is a utility that extends jest with additional "matchers". Essentially additional methods I can use when I expect() on an element. Some of these include toBeVisible, toBeValid, toBeInTheDocument, toBeChecked, toHaveStyle and toHaveClass.

For this lesson use toHaveClass to ensure that our table is in the correct state. First it will show no special class when we first attempt to checkout, then after we attempt to checkout it will show the checkoutLoading class and then after our API call completes trying to checkout with no items, we'll see our table will have the class checkoutError.

In general testing for class names is a little bit brittle, but if we use this technique sparingly, such as in cases where we use classes to highlight an error. I think the approach is worth the risk.


We use the .not property of expect here to explicitly say that our table should not have a class. This property will reverse whatever our matcher is looking for and can come in handy in a lot of situations.

michauxkelley@gmail.com Kelley
~ 11 months ago

Around 1:47 the code for the test 'cannot checkout with an empty cart' should be:

await userEvent.click(checkout); expect(table).toHaveClass('checkoutLoading'); waitFor(() => { screen.findByText('Cart must not be empty'); expect(table).toHaveClass('checkoutError'); });

michauxkelley@gmail.com Kelley
~ 11 months ago

Newlines didn't show in the above comment, so just make sure to put a new line after each semi-colon

michauxkelley@gmail.com Kelley
~ 11 months ago

Actually not sure about this one, but this is the only bit of the test I could get to pass:

test('Cannot checkout with an empty cart', async () => {

checkoutSpy.mockRejectedValueOnce(new Error('Cart must not be empty'));

const { debug } = renderWithContext(<Cart />);

const checkout = screen.getByRole('button', { name: 'Checkout' });

let table = screen.getByRole('table');

expect(table).not.toHaveClass('checkoutLoading');

await userEvent.click(checkout);

// This doesn't work // expect(table).toHaveClass('checkoutLoading');

expect(table).toHaveClass('checkoutError');

screen.getByText('Cart must not be empty');

});

michauxkelley@gmail.com Kelley
~ 11 months ago

So I was actually able to get the test to pass all the conditions using:

test('Cannot checkout with an empty cart', async () => {

checkoutSpy.mockRejectedValueOnce(new Error('Cart must not be empty'));

renderWithContext(<Cart />);

const checkout = screen.getByRole('button', { name: 'Checkout' });

let table = screen.getByRole('table');

expect(table).not.toHaveClass('checkoutLoading');

await waitFor(async () => {

await userEvent.click(checkout);

expect(table).toHaveClass('checkoutLoading');

});

setTimeout(() => {

expect(table).toHaveClass('checkoutError');

screen.getByText('Cart must not be empty');

}, 10);

});

michauxkelley@gmail.com Kelley
~ 11 months ago

My previous code is wrong because the timeout happens after the test has already run.

And I wasn't able to get it to work with mockRejectedValueOnce. (Do you know why?)

Sorry, the correct test code is:

test('Cannot checkout with an empty cart', async () => {

checkoutSpy.mockRejectedValue(new Error('Cart must not be empty'));

renderWithContext(<Cart />);

const checkout = screen.getByRole('button', { name: 'Checkout' });

const table = screen.getByRole('table');

expect(table).not.toHaveClass('checkoutLoading');

await waitFor(() => {

userEvent.click(checkout);

expect(table).toHaveClass('checkoutLoading');

});

await new Promise((r) => setTimeout(r, 10));

expect(table).toHaveClass('checkoutError');

screen.getByText('Cart must not be empty');

});

michauxkelley@gmail.com Kelley
~ 11 months ago

userEvent.click(checkout); doesn't need to be in the waitFor