testing-library / jest-dom

:owl: Custom jest matchers to test the state of the DOM
https://testing-library.com/docs/ecosystem-jest-dom
MIT License
4.44k stars 400 forks source link

screen.findByText not finding text fetched from mocked axios call using msw #382

Closed LuisBarroso37 closed 3 years ago

LuisBarroso37 commented 3 years ago

Relevant code or config:

UsersTable.test.tsx

import UsersTable from './UsersTable';
import { render, screen, waitFor } from '../../test/setup';
import { server, rest } from '../../test/server';

describe('Testing UsersTable component', () => {
  it('renders the component', async () => {
    render(<UsersTable />);

    await waitFor(() => {
      const tableHeader = screen.getByRole('row', {
        name: 'Id Email Company',
      });
      expect(tableHeader).toBeInTheDocument();
    });
  });

  it('renders error message if not able to fetch models from server', async () => {
    // Get back error message from server
    server.use(
      rest.get('http://localhost:9000/api/users', (req, res, ctx) => {
        return res(
          ctx.status(400),
          ctx.json({ message: 'Access to account denied' })
        );
      })
    );

    render(<UsersTable />);

    await waitFor(() => {
      // Expect not find any rows in the table
      const companyNameCells = screen.queryByText(/Rootit/i);
      expect(companyNameCells).toBeNull();

      // Expect alert message to pop up
      const alertMessage = screen.getByText('Access to account denied');
      expect(alertMessage).toBeInTheDocument();
    });
  });

  it('renders models that are fetched from server in the table', async () => {
    render(<UsersTable />);

    const emailOneCell = await screen.findByText('test1@test.com');
    const emailTwoCell = await screen.findByText('test2@test.com');

    expect(emailOneCell).toBeDefined();
    expect(emailTwoCell).toBeDefined();
  });
});
mws setup

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const users = [
  {
    id: 'abc',
    email: 'test1@test.com',
    company: 'Test',
  },
  {
    id: 'def',
    email: 'test2@test.com',
    company: 'Test',
  },
  {
    id: 'ghi',
    email: 'test3@test.com',
    company: 'Test',
  },
  {
    id: 'jkl',
    email: 'test4@test.com',
    company: 'Test',
  },
];

const handlers = [
  rest.get('http://localhost:9000/api/users', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(users));
  }),
];

// This configures a request mocking server with the given request handlers
const server = setupServer(...handlers);

export { server, rest };
UsersTable.tsx

import { useCallback, useEffect, useRef, useState, useContext } from 'react';
import { DataGrid, GridColDef } from '@material-ui/data-grid';
import { Box } from '@material-ui/core';

import TableTooltip from '../../components/Tooltip/Tooltip';
import { useStyles } from './UsersTable.styles';
import { ActionTypes, AppContext } from '../../context/app-context';
import { ApiResult } from '../../api/ApiResultInterface';
import { ApiService } from '../../api/ApiSevice';

type User = {
  id: string;
  email: string;
  company: string;
};

const UsersTable = () => {
  const [users, setUsers] = useState<User[]>([]);
  const classes = useStyles();
  const { dispatch } = useContext(AppContext);

  // Used to not update state if component is unmounted before data fetch is complete
  let _isMounted = useRef(true);

  const setAlert = useCallback(
    (msg: string, status: number) => {
      dispatch({
        type: ActionTypes.SetAlertMessage,
        payload: new ApiResult(msg, status),
      });
      dispatch({ type: ActionTypes.OpenAlertMessage, payload: true });
    },
    [dispatch]
  );

  useEffect(() => {
    return () => {
      _isMounted.current = false;
    };
  }, []);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await ApiService.getUsers();

        // Do nothing if component has unmounted while fetching data
        if (!_isMounted.current) {
          return;
        }

        setUsers(response.data);
      } catch (err) {
        if (err.response) {
          const status = err.response.status;
          const error = err.response.data;

          setAlert(error.message, status);
        } else {
          setAlert(err.message, 400);
        }
      }
    };

    fetchUsers();
  }, [setAlert]);

  // Table columns
  const columns: GridColDef[] = [
    {
      field: 'id',
      headerName: 'Id',
      description: 'Id',
      headerAlign: 'center',
      align: 'center',
      flex: 2,
      renderCell: (cellParams) => {
        return (
          <TableTooltip title={cellParams.value as string}>
            <div className={classes.cellText}>{cellParams.value}</div>
          </TableTooltip>
        );
      },
      renderHeader: (headerParams) => {
        return (
          <TableTooltip title={headerParams.colDef.description as string}>
            <div className={classes.cellText}>
              {headerParams.colDef.description}
            </div>
          </TableTooltip>
        );
      },
    },
    {
      field: 'email',
      description: 'Email',
      headerName: 'Email',
      headerAlign: 'center',
      align: 'center',
      flex: 2,
      renderCell: (cellParams) => {
        return (
          <TableTooltip title={cellParams.value as string}>
            <div className={classes.cellText}>{cellParams.value}</div>
          </TableTooltip>
        );
      },
      renderHeader: (headerParams) => {
        return (
          <TableTooltip title={headerParams.colDef.description as string}>
            <div className={classes.cellText}>
              {headerParams.colDef.description}
            </div>
          </TableTooltip>
        );
      },
    },
    {
      field: 'company',
      description: 'Company',
      headerName: 'Company',
      headerAlign: 'center',
      align: 'center',
      flex: 1,
      renderCell: (cellParams) => {
        return (
          <TableTooltip title={cellParams.value as string}>
            <div className={classes.cellText}>{cellParams.value}</div>
          </TableTooltip>
        );
      },
      renderHeader: (headerParams) => {
        return (
          <TableTooltip title={headerParams.colDef.description as string}>
            <div className={classes.cellText}>
              {headerParams.colDef.description}
            </div>
          </TableTooltip>
        );
      },
    },
  ];

  return (
    <Box boxShadow={3} className={classes.usersTableContainer}>
      <DataGrid
        rows={users}
        loading={users === []}
        columns={columns}
        pageSize={100}
        className={classes.usersTable}
        checkboxSelection={false}
        localeText={{
          footerRowSelected: () => '',
        }}
      />
    </Box>
  );
};

export default UsersTable;

What happened:

This is the error message: image image

Problem description:

I expected screen to find the fetched data in the DataGrid component but it does not. I have written other very similar tests with the same setup and they all pass so I do not understand why this one does not.

I console logged the response that I get back from the mocked axios call and I see the data. I know that the state is being updated but for some reason screen.findByText() does not find it.

Can you help me figure out why this is happening?

eps1lon commented 3 years ago

Thanks for the feedback.

Please provide a CodeSandbox or a link to a repository on GitHub. Otherwise these types of issues are incredibly hard for us to debug.

Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve

LuisBarroso37 commented 3 years ago

@eps1lon I tried to recreate it as best as I could. I had to do some changes to make it work on CodeSandbox.

Here is the link: https://codesandbox.io/s/create-react-app-typescript-forked-9z498?file=/src/UsersTable.test.tsx

gnapse commented 3 years ago

If the problem is screen.findByText() then it is not jest-dom. Maybe we should move this to @testing-library/dom-testing-library? Let me know and I can do it.

LuisBarroso37 commented 3 years ago

@eps1lon, @gnapse,

Is the CodeSandbox enough? Are you able to figure out what the issue is?