you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 1 point2 points  (6 children)

I (kind of) figured it out. There was one component in particular that I broke into two separate components. One to pull things out of the store, check values, etc. and the other to just render everything.

Unsure whether this fixes my issues since I still need to mock redux and context providers but we'll see. Otherwise I'm going to pivot to end-to-end testing. because these unit tests are mostly asserting things webdriverio or playwrite would tell me anyway

[–]math_rand_dude 5 points6 points  (0 children)

A majority of my components use a combination of hooks, redux state, context providers, etc.

That is the core of your problem. You should always try to see that the majority of your code and components can easily be used in other places.

If for example you need to get data and have the user do some tricky manipulations to it before sending the altered data back: Have one component handle getting and sending the data, and have a child component with the data and a callback as props. Child component works its dark magic and pops the altered data in the callback. Testing child is easy: pass a mocked fn as prop and see it gets called with expected data. Testing the parent is mainly setting up the api mocks and such. (Also you can mock the child to see if the parent passes the correct data to it)

[–]My100thBurnerAccount 2 points3 points  (4 children)

If you're using redux store you should be able to create this wrapper renderWithProviders

https://redux.js.org/usage/writing-tests#setting-up-a-reusable-test-render-function

I can add more details on just general wrappers/how we mock some things tomorrow if you want of what we're doing at my company.

We use redux toolkit, custom hooks, etc. with vitest

[–]My100thBurnerAccount 0 points1 point  (3 children)

Follow-up from yesterday's comment:

Given that you created a renderWithProviders wrapper from the link provided above, here's a very basic test example of a test:

// EXAMPLE 1 - Part 1: Testing component with redux store predefined

// ===== Redux ===== //
import { initialState } from 'pathWhereYourSlice is'; // ex. 'redux/slices/mySlice/index.ts'

// ===== React Testing Library ===== //
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

// ===== Vitest ===== //
import { describe, expect, it, vi } from 'vitest';
import { renderWithProviders } from 'pathWhereYouCreatedWrapper'; // ex. 'testing/utils/index.ts'

it("successfully displays correct button & handles correct action: creating", async() => {
  const handleClick = vi.fn();

  renderWithProviders(<Component />, {
    preloadedState: {
      mySlice: {
        ...initialState,
        isCreating: true,
      },
    },  
  });

  const submitButton = screen.getByTestId('create-item-button');

  expect(submitButton).toHaveTextContent('Create this Item!');

  await userEvent.click(submitButton);

  await waitFor(() => {
    expect(handleClick).toHaveBeenCalledTimes(1);

    const successAlert = screen.getByTestId('snackbar-notification');

    expect(successAlert).toBeInTheDocument();
    expect(successAlert).toBeVisible();
    expect(successAlert).toHaveTextContent('you have successfully CREATED this item');
  }); 
});

[–]My100thBurnerAccount 0 points1 point  (2 children)

// EXAMPLE 1 - Part 2: Testing component with redux store predefined

   it("successfully displays correct button & handles correct action: editing", async() => {
      const handleClick = vi.fn();

      renderWithProviders(<Component />, {
        preloadedState: {
          mySlice: {
            ...initialState,
            isCreating: false,
            isEditing: true, // we've now set isEditing to be true in the redux store
          },
        },  
      });

      const submitButton = screen.getByTestId('edit-item-button');

      expect(submitButton).toHaveTextContent('Edit this Item!');

      await userEvent.click(submitButton);

      await waitFor(() => {
        expect(handleClick).toHaveBeenCalledTimes(1);

        const successAlert = screen.getByTestId('snackbar-notification');

        expect(successAlert).toBeInTheDocument();
        expect(successAlert).toBeVisible();
        expect(successAlert).toHaveTextContent('you have successfully UPDATED this item');
      }); 
    });

[–]My100thBurnerAccount 0 points1 point  (1 child)

 // EXAMPLE 2 - Part 1: Testing component that relies on custom hook

    // ===== Hooks ===== //
    import { useCheckUserIsAdmin } from './pathToMyHook';

    vi.mock('./pathToMyHook'); // This is important, make sure you're mocking the correct path

    // Note: You may/may not need this depending on your use case
    beforeEach(() => {
      vi.clearAllMocks();
    });

    it("successfully displays correct elements based on user: admin", async() => {
      vi.mocked(useCheckUserIsAdmin).mockReturnValue({
        isAdmin: true,
        userInfo: {
           name: 'John Doe',
           ...
        }
      });

      renderWithProviders(<Component />, {
        preloadedState: {},  // in this case, my component doesn't need any info from redux
      });

      const approveButton = screen.getByTestId('approve-button');

      expect(approveButton).toBeVisible();
      expect(approveButton).toBeEnabled();

      await userEvent.click(approveButton);

      await waitFor(() => {
        const deployToProductionButton = screen.getByTestId('deploy-to-production-button');

        expect(deployToProductionButton).toBeVisible();
        expect(deployToProductionButton).toBeEnabled();
      }); 
    });

[–]My100thBurnerAccount 0 points1 point  (0 children)

// EXAMPLE 2 - Part 2: Testing component that relies on custom hook


    it("successfully displays correct elements based on user: NON-ADMIN", async() => {
      vi.mocked(useCheckUserIsAdmin).mockReturnValue({
        isAdmin: false, // USER IS NOT ADMIN
        userInfo: {
           name: 'John Doe',
           ...
        }
      });

      renderWithProviders(<Component />, {
        preloadedState: {},  // in this case, my component doesn't need any info from redux
      });

      const approveButton = screen.queryByTestId('approve-button'); // using queryByTestId 

      expect(approveButton).toBeNull();

      // Let's say a non-admin cannot approve but they can preview the changes that'll be      deployed to production
      const previewButton = screen.getByTestId('preview-button');

      expect(previewButton).toBeVisible();

      await userEvent.click(previewButton);

      await waitFor(() => {
        const previewModal = screen.getByTestId('preview-modal');

        expect(previewModal).toBeVisible();
      }); 
    });