beekai-oss / little-state-machine

📠 React custom hook for persist state management
https://lrz5wloklm.csb.app/
MIT License
1.47k stars 53 forks source link

unable to create mock provider for Jest+React testing Lib #111

Closed Dujota closed 2 years ago

Dujota commented 2 years ago

So i am trying to setup up testing for my project and all works well except when i try to create a mock/test provider for our test suite.

(nothing too fancy just Next.js 11 + Jest + RTL)

The main issue is that i want to move away from mocking the return value of useStateMachine in favor of just letting the lib do its job and i test the result of the render.

This is also hindering my ability to test click events since they rely on LSM state changes. (ie hook form controlled by lsm).

So i have working providers for react query, i would assume that they would work the same. i did a test with just creating a React.Context provider and that works just fine.

So the main error i am getting is that createStore({...mockStore}) comes back with

// works just fine with the query provider etc. 
// When i add the provider to wrap other existing providers (ie query client for react-query) it fails

import * as React from 'react';
import { StateMachineProvider, createStore } from 'little-state-machine';
import { initialState } from '../../store/states';
import { QueryClient, QueryClientProvider } from 'react-query';

import { render } from '@testing-library/react';

const createTestQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });

const mockStore = () =>
  createStore({
    ...initialState
  });

// HOC for rendering our ui components with the query client along with testing configs
export function renderWithClient(ui) {
  mockStore();
  const testQueryClient = createTestQueryClient();

  const { rerender, ...result } = render(
    <StateMachineProvider>
      <QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider>
    </StateMachineProvider>
  );
  return {
    ...result,
    rerender: (rerenderUi) =>
      rerender(
        <StateMachineProvider>
          <QueryClientProvider client={testQueryClient}>{rerenderUi}</QueryClientProvider>{' '}
        </StateMachineProvider>
      ),
  };
}

// Query Client Context wrapper for our tests, use this separately when we want to
// use it to render hooks outiside components for testing
export function createWrapper() {
  const testQueryClient = createTestQueryClient();
  mockStore();

  return ({ children }) => (
    <StateMachineProvider>
      <QueryClientProvider client={testQueryClient}>{children}</QueryClientProvider>;
    </StateMachineProvider>
  );
}
}

i get the below error, which basically tells me that little state machine is undefined, and checking console confirms same.

 TypeError: (0 , _littleStateMachine.createStore) is not a function

      27 |
      28 | const mockStore = () =>
    > 29 |   createStore({
         |   ^
      30 |     user,
      31 |     points,
      32 |     transactions,

      at mockStore (__mocks__/utils/index.js:29:3)
      at renderWithClient (__mocks__/utils/index.js:44:3)

This is how i would normally create all my mockProviders (for which all except LSM are 100% working)

Any ideas/thoughts? Has anyone created test suites with LSM provider?

JamesWong1999 commented 2 years ago
describe('test', () => {
  it('should works', async () => {
    const App = () => {};
    const Root = () => {
      createStore({
        data: {}
      });

      return (
        <StateMachineProvider>
          <App />
          <App />
          <App />
          <App />
        </StateMachineProvider>
      );
    };

    render(<Root />);
  });

could you do something like above?

Dujota commented 2 years ago
describe('test', () => {
  it('should works', async () => {
    const App = () => {};
    const Root = () => {
      createStore({
        data: {}
      });

      return (
        <StateMachineProvider>
          <App />
          <App />
          <App />
          <App />
        </StateMachineProvider>
      );
    };

    render(<Root />);
  });

could you do something like above?

I could try to setup a provider on each test, but the idea is to automate the boilerplate portion before each test. Works fine for all others except LSM. There's something i am probably missing here.

bluebill1049 commented 2 years ago

each test probably should have its own provider, what @JamesWong1999 provided is the correct approach.

Dujota commented 2 years ago

each test probably should have its own provider, what @JamesWong1999 provided is the correct approach.

So i guess what I am confused about, how does this work for all other providers but not LSM. I can make this work with React.Context since it just accepts a store.

It doesnt even pick up the modules createStore function, so its techinically running on each test startup. (ie creates a new provider for each test case)

bluebill1049 commented 2 years ago

That's more of a testing lib question than LSM, LSM is just leverage on top of React context API.

Dujota commented 2 years ago

That's more of a testing lib question than LSM, LSM is just leverage on top of React context API.

i feel like this should work, it is pretty standard boilerplate for example:

function renderWithProviders(ui, { reduxState, locale = "en" }) {
  const store = createStore(reducer, reduxState || initialState);
  return (
    <ThemeProvider>
      <ReduxProvider store={store}>
        <IntlProvider locale={locale}>{ui}</IntlProvider>
      </ReduxProvider>
    </ThemeProvider>
  );
}

The redux example is pretty much in line with what I was trying to do (very similar to how i would setup React.Context)

I am going to try to refactor the _app.js providers (which includes LSM) into one <AllProviders > {children} </AllProviders> component and see if that can circumvent this issue