Search code examples
reactjsreact-hooksreact-testing-libraryrtk-query

Unit testing for mutation RTK Query with fixedCacheKey


I'm currently running RTK Queries for my project and i'm stuck on how to test ComponentTwo.

The below is from the RTK Query doco and is it the exact same scenario I'm in.

For me, in ComponentOne I used the updatePost trigger function to get a response from the API and use that response result in ComponentTwo (ComponentTwo is one a different page)

What is the best way to test ComponentTwo?

Should I mock useUpdatePostMutation using spyOn? or is there a more 'elegant' way?

Thank you very much!

export const ComponentOne = () => {
  // Triggering `updatePostOne` will affect the result in both this component,
  // but as well as the result in `ComponentTwo`, and vice-versa
  const [updatePost, result] = useUpdatePostMutation({
    fixedCacheKey: 'shared-update-post',
  })

  return <div>...</div>
}

export const ComponentTwo = () => {
  const [updatePost, result] = useUpdatePostMutation({
    fixedCacheKey: 'shared-update-post',
  })

  return <div> {result.data.post}</div>
}

Solution

    1. You don't need to test the behavior of the fixedCacheKey option, RTKQ already tested it.

    2. You should use msw, axios-mock-adapter and so on to mock the API based on the library you use to handle the requests.

    3. Assert what will be rendered of ComponentTwo after calling the updatePost in ComponentOne.

    e.g.(There is no real HTTP request, the endpoint returns the argument as data for demostration)

    index.tsx:

    import React from 'react';
    import { createApi } from '@reduxjs/toolkit/query/react';
    
    export const api = createApi({
      async baseQuery(arg: string | Promise<string>) {
        return { data: await arg };
      },
      endpoints: (build) => ({
        updatePost: build.mutation<string, string | Promise<string>>({
          query: (arg) => arg,
        }),
      }),
    });
    const { useUpdatePostMutation } = api;
    
    export const ComponentOne = ({ name }: { name: string }) => {
      const [updatePost, result] = useUpdatePostMutation({
        fixedCacheKey: 'shared-update-post',
      });
    
      return (
        <div data-testid={name}>
          <div data-testid="status">{result.status}</div>
          <div data-testid="data">{result.data}</div>
          <div data-testid="originalArgs">{String(result.originalArgs)}</div>
          <button data-testid="trigger" onClick={() => updatePost(name)}>
            trigger
          </button>
        </div>
      );
    };
    
    export const ComponentTwo = ({ name }: { name: string }) => {
      const [updatePost, result] = useUpdatePostMutation({
        fixedCacheKey: 'shared-update-post',
      });
    
      return (
        <div data-testid={name}>
          <div data-testid="status">{result.status}</div>
          <div data-testid="data">{result.data}</div>
          <div data-testid="originalArgs">{String(result.originalArgs)}</div>
          <button data-testid="trigger" onClick={() => updatePost(name)}>
            trigger
          </button>
          <button data-testid="reset" onClick={result.reset}>
            reset
          </button>
        </div>
      );
    };
    

    index.test.tsx:

    import { act, getByTestId, render, screen, waitFor } from '@testing-library/react';
    import { ComponentOne, ComponentTwo, api } from '.';
    import React from 'react';
    import { configureStore } from '@reduxjs/toolkit';
    import { Provider } from 'react-redux';
    
    describe('77170397', () => {
      test('should pass', async () => {
        const store = configureStore({
          reducer: {
            [api.reducerPath]: api.reducer,
          },
          middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
        });
        render(
          <Provider store={store}>
            <ComponentOne name="c1" />
            <ComponentTwo name="c2" />
          </Provider>,
        );
    
        const c1 = screen.getByTestId('c1');
        const c2 = screen.getByTestId('c2');
        expect(getByTestId(c1, 'status').textContent).toBe('uninitialized');
        expect(getByTestId(c2, 'status').textContent).toBe('uninitialized');
    
        act(() => {
          getByTestId(c1, 'trigger').click();
        });
    
        await waitFor(() => expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'));
    
        expect(getByTestId(c1, 'data').textContent).toBe('c1');
        expect(getByTestId(c2, 'status').textContent).toBe('fulfilled');
        expect(getByTestId(c2, 'data').textContent).toBe('c1');
        expect(getByTestId(c2, 'status').textContent).toBe('fulfilled');
      });
    });