Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] 馃殌 Testing utilities for zustand #271

Closed
3nvi opened this issue Jan 6, 2021 · 14 comments
Closed

[Proposal] 馃殌 Testing utilities for zustand #271

3nvi opened this issue Jan 6, 2021 · 14 comments
Labels
help wanted Please someone help on this

Comments

@3nvi
Copy link
Contributor

3nvi commented Jan 6, 2021

Background

zustand is an amazing package that doesn't depend on React Context providers at all. The stores live "outside" of React and can be accessed from everywhere, even outside of React components.

Because of this, when testing React components via a testing library, the states of the stores don't reset across tests. That means that the code of one test may affect the outcome of another test. It also means that within a test suite (i.e. describe block), test order matters, since a test might modify the state of a store that another test may be depending upon.

There's currently no official way of resetting store state before/after each test. A custom solution would include manually extracting the initialState of a store and using it to force-set a store's state before each test run:

const initialStoreState = useStore.getState();

describe('Suite', () => {
  
  beforeEach(() => {
    useStore.setState(initialStoreState, true);
  });

  ... // tests

});

This can quickly become tedious since:

  1. You have to manually do that for each test suite/file
  2. You have to do that for each and every store in your app or remember the exact stores your tests will be using

An alternative solution involves "mocking" zustand and automatically resetting all stores after each test by keeping track of all stores in the app. This involves creating a __mocks__/zustand.js file and putting the following code in there:

import actualCreate from 'zustand';

// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set();

// when creating a store, we get its initial state, create a reset function and add it in the set
const create = createState => {
  const store = actualCreate(createState);
  const initialState = store.getState();
  storeResetFns.add(() => store.setState(initialState, true));
  return store;
};

// Reset all stores after each test run
afterEach(() => {
  storeResetFns.forEach(resetFn => resetFn());
});

This has the benefits of:

  1. Not having to modify your source code or tests in any shape or form
  2. Not having to do anything for each new store you create

Goal

Have an officially supported way of testing zustand, while limiting the developer's effort to achieve that

Proposal

We create a separate package named zustand/testing which will contain a polished & safe version of the snippet above. From there there are 2 options:

Option 1 - Automatic mocking

This option automatically mocks zustand's default export, without the developer needing to create any mock files.

Whenever process.env.NODE_ENV has a value of test (which currently stands true for all major test runners), zustand will silently replace the default create function with a "mocked" version of create (available via zustand/testing) that will enable the reset functionality portrayed above.

In order to be backwards compatible & avoid any breaking changes, this can be controlled by an additional additional ENV variable named ZUSTAND_SKIP_AUTO_RESET which will default to true in the current major version and to false in the next one.

Option 2 - Manual mocking

This option requires developers to explicitly opt-in to the mocking behavior. Specifically, developers will have to import zustand/testing within their setup file in order to have the mocking capability enabled.

This removes the need for process.env.NODE_ENV and process.env.ZUSTAND_SKIP_AUTO_RESET, but requires an explicit opt-in.

Related Links

#242

@wolframkriesing
Copy link

I am using a zustand/vanilla store, that is testable on its own, it exports action functions, so I can have fast tests that really worry about the business logic that work with the store. Connecting it to react is just a matter of

import vanillaZustand from 'zustand/vanilla';
const createVanilla = typeof vanillaZustand === 'function' ? vanillaZustand : vanillaZustand.default; // I dont know how to do without this, help welcome

export const newStore = () => {
    const initialState = {};
    return createVanilla(() => initialState);
};

const store = newStore();

// make it useable with React
import create from 'zustand';
const useTheStore = create(vanillaStore),

here is some tests (kinda kata) I am writing to show how zustand can be used, I am basically writing this to learn (and be able to re-learn) zustand's API and document it (initally for me)
https://github.com/tddbin/katas/blob/master/katas/libraries/zustand/vanilla-store.js

@dai-shi
Copy link
Member

dai-shi commented May 5, 2021

zustand/context in v3.5.0 may or may not help.

@dai-shi dai-shi added the help wanted Please someone help on this label Jul 30, 2021
@dai-shi
Copy link
Member

dai-shi commented Aug 13, 2021

I would hope someone to explore zustand/context for testing.

Meanwhile, someone can also work on elaborating/improving https://github.com/pmndrs/zustand#testing and https://github.com/pmndrs/zustand/wiki/Testing.

Any volunteers?

@3nvi
Copy link
Contributor Author

3nvi commented Aug 13, 2021

zustand/context in v3.5.0 may or may not help.

It helps, but only for the components that are wrapped in a context. Not all componets will have or depend on a provider above them, thus the original issue is still valid

I would hope someone to explore zustand/context for testing.

Meanwhile, someone can also work on elaborating/improving https://github.com/pmndrs/zustand#testing and https://github.com/pmndrs/zustand/wiki/Testing.

Any volunteers?

What are your thoughts?

@dai-shi
Copy link
Member

dai-shi commented Aug 13, 2021

What are your thoughts?

Creating/polishing docs and recipes in the wiki page to resolve this issue.

@flq
Copy link

flq commented Oct 27, 2021

Just starting out with Zustand, so I cannot comment yet on how tedious it becomes - we wrote this little helper here:

export function initZustandStoreCleanup(hook: UseBoundStore<any, StoreApi<any>>) {
  let initialState = hook.getState();
  return () => {
    // cleanup here is from react-testing: if components are still "alive" after a test, resetting the state of the hook
    // causes warnings that changes are happening without having it wrapped in "act"
    cleanup();
    hook.setState(initialState);
  };
}

which you can the use like that:

let resetCache: SimpleHandler;

beforeAll(() => {
  resetCache = initZustandStoreCleanup(useNewsFeedCache);
});

afterEach(resetCache);

this can be easily extended to accept a number of such hooks, so...I guess usage will pick up gradually now, we shall see how tedious this becomes

@mx-bernhard
Copy link

mx-bernhard commented Jan 1, 2022

jest.isolateModules() might also be an option to reset the state.

@alekangelov
Copy link
Member

Here's my branch with the following changes:
https://github.com/alekangelov/zustand/tree/271

  • Revised docks for testing + added example for tests and best practices
  • Added /examples/site and /examples/react-jest <- they symlink back to dist
  • Added a .dev rollup config so people can work as they have an example open
  • Added dev command to package.json for rollup watch mode and disabled the aliasing so that importing doesn't get fudged up
  • Change the config to be injected on build script instead of all the individual builds
  • Added setup-tests that automatically mocks the the create function <- uses @testing-library/react down below are my concerns about this approach, need input

Also in a bit of a conundrum here, because zustand is a pretty library agnostic package, yet act is exported from @testing-library/react and it depends on the ui library you're using. Looking for alternatives currently, but one would be that the setup-tests is split into different categories and they call different versions of testing-library.

Another option would be to leave mocking up to the devs and we can just provide examples for jest like the one written here:

https://github.com/alekangelov/zustand/blob/271/src/setup-tests.ts

@dai-shi
Copy link
Member

dai-shi commented May 21, 2022

can you open a draft pr?

we'd like to have a comprehensive doc in ./docs/testing.md instead of increasing readme content.

@alekangelov
Copy link
Member

will do, need to write the docs/tests for the bound store, but will give you a solid idea of where I'm going with the changes

@alekangelov
Copy link
Member

#968

here it is, complete with examples/docs for bound stores and global ones.

@dai-shi
Copy link
Member

dai-shi commented May 24, 2022

#968 (comment)

please someone help.

@gsouf
Copy link

gsouf commented Sep 19, 2022

Looks like this is done with https://github.com/pmndrs/zustand/blob/main/docs/guides/testing.mdx

@dai-shi
Copy link
Member

dai-shi commented Sep 19, 2022

We probably want some more practices in docs.
#968 seemed nice one.

That said, this issue is old. So, let's close it.
Please open a new discussion to improve testing docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Please someone help on this
Projects
None yet
Development

No branches or pull requests

7 participants