2

I am using React-query and trying to test it, the data is called in the parent and passed into a child component to render a list of items. I have setup MSW, however when I try my test as:

const queryClient = new QueryClient()

it('Should give a response', async () => {
    render(<QueryClientProvider client={queryClient}><Component /></QueryClientProvider>);
    expect(await screen.findByTestId('test-data')).toBeInTheDocument();
})

I get this:

Unable to find an element by: [data-testid="test-data"]

<body>
  <div>
    <div
      class="mt-5"
    >
      Loading data ...
    </div>
  </div>
</body>

The parent component is like so:

const Component1: React.FunctionComponent = () => {
    const [fetchData, setFetchData] = React.useState<IData[]>([]);

    const queryKey = 'mainData';

    const getData = async (): Promise<any> => {
            await new Promise(resolve => setTimeout(resolve, 1000))
            const response = await axios.get(`xxxxxxx}`).then(res => res.data);
            return response;
    }

    const { data: result, status, isRefetching, error, refetch }: any = useQuery(queryKey, getData,
        {
            refetchOnWindowFocus: true,
            staleTime: 0,
            cacheTime: 0,
            refetchInterval: 0,
        }
    );
 
    return (
        <>
            {status === 'error' && (
                <div className="mt-5">{error.message}</div>
            )}
            {status === 'loading' && (
                <div className="mt-5">Loading data ...</div>
            )}
            {status === 'success' && (
                <div className="row dashboard">
                    <ChildComponent data={fetchData} data-testid="test-data"/> // data passed to this component
                    <br />
                </div>
            )}
            {isRefetching ? 'Updating...' : null} 
        </>
    )
};

export default Component1;

setUptests:

import '@testing-library/jest-dom';

import { server } from './mocks/server'
// Establish API mocking before all tests.

beforeAll(() => server.listen())
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.

afterEach(() => server.resetHandlers())

// Clean up after the tests are finished.
afterAll(() => server.close())

Handlers

import { rest } from 'msw';

export const handlers = [
    rest.get('https://xxxxxxxx', (req, res, ctx) => {
        return res(
            ctx.json([
                {
                    "titleNumber": 499,
                    "model": "Yes Model",
                },
                {
                    "titleNumber": 434,
                    "model": "No Model",
                },
            ])
        );
    }),
];

server

import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)

Am I testing in the wrong place?

Sole
  • 3,100
  • 14
  • 58
  • 112
  • Are you sure that the MSW is working? – Richard Hpa Oct 29 '21 at 00:37
  • How would I verify that? `it('Should give a response', async () => { render(); expect(await screen.findByText('Loading data ...')).toBeInTheDocument(); })` this test works. – Sole Oct 29 '21 at 00:40
  • 1
    yeah but thats not testing to see if the api call itself is returning something. You need to test to see if getData() is actually returning something from msw. Can you post a link to a github repo or something? – Richard Hpa Oct 29 '21 at 00:41
  • I have updated the question with the handlers and msw setup – Sole Oct 29 '21 at 00:44
  • 1
    I think there is something missing, could you post a github link or something? would be easier to resolve – Richard Hpa Oct 29 '21 at 01:02
  • I think I know what it is, the api is behind a security platform called Scala Ztron which means it needs to be authenticated, so I think I can't mock the response url, as it's secured - hence why it is just in the loading ... state – Sole Oct 29 '21 at 01:03
  • 1
    This is getting into a discussion but you should be able to do that with msw thats meant to catch the api call so it shouldnt have to go to the auth. That means the msw probably isnt working – Richard Hpa Oct 29 '21 at 01:08
  • If I change the url of the request in the handler, I do get this warning error: `[MSW] Warning: captured a request without a matching request handler:` so I think it is working – Sole Oct 29 '21 at 01:10
  • 1
    This was a really odd issue. Just updated my answer – Richard Hpa Oct 29 '21 at 02:50

1 Answers1

1

Updated Answer

OK this was a really odd issue. I replicated it and got the same thing even when it shouldn't. I think the issue might be to do with react-query and testing, rather than msw. To resolve it I just told jest to useFakeTimers and then it worked

describe('should return data', () => {
    beforeEach(() => {
        jest.useFakeTimers();
    });

    it('Should give a response', async () => {
        render(
            <QueryClientProvider client={queryClient}>
                <App />
            </QueryClientProvider>
        );
        expect(await screen.findByTestId('test-data')).toBeInTheDocument();
        // screen.debug();
    });
});

I think the issue is because you are passing data-testid="test-data" as a prop onto ChildComponent and i'm guessing you aren't using that inside of the component that which would mean it isn't actually being attached to any DOM element. That would explain why it says it cant find the element because unless you are using it inside of ChildComponent then it isn't being used.

Try adding it to an element that is a don node rather than a the component, like the div which it is wrapped in.

{status === 'success' && (
    <div className="row dashboard" data-testid="test-data">
        <ChildComponent data={fetchData} />
        <br />
    </div>
)}
Richard Hpa
  • 2,353
  • 14
  • 23
  • Unable to find an element by: [data-testid="test-data"].. I wonder if I moved the rest mock into the test file itself. – Sole Oct 29 '21 at 00:56
  • It's not getting passed the loading part – Sole Oct 29 '21 at 00:58
  • That did work, so what exactly is `'jest.useFakeTimers();` not come across that? – Sole Oct 29 '21 at 09:34
  • 1
    useFakeTimers just tells anything that would use time (ie setTimeout, setInterval) to fake it. rather than use the actual thing. So in your situation, you are using setTimeout so rather than using that it fakes it. – Richard Hpa Oct 30 '21 at 03:50