zustand icon indicating copy to clipboard operation
zustand copied to clipboard

Trying to migrate to v4 but hitting issue with mocking

Open httpete opened this issue 3 years ago • 21 comments

I was using the mocks/zustand.ts that contained the boilerplate from the doc, but now I get TypeError: store.getState is not a function

import { act } from 'react-dom/test-utils';
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(() => {
  act(() => storeResetFns.forEach(resetFn => resetFn()));
});

export default create;

httpete avatar Jul 05 '22 20:07 httpete

Hm, not sure, but does this help?

const store = actualCreate(createState || () => ({}));

dai-shi avatar Jul 05 '22 23:07 dai-shi

Guessing but you'll probably have to change your mocked create to allow the curried usage...

const create = f =>
  f === undefined ? createImpl : createImpl(f)

const createImpl = createState => {
  const store = actualCreate(createState)
  const initialState = store().getState()
  storeResetFns.add(() => store.setState(initialState, true))

  return store
}

devanshj avatar Jul 06 '22 09:07 devanshj

OK Ill try soon thank you. I absolutely love the simplicity of Zustand 3, but I am finding that Zustand 4 just seems to be harder to use and I am not sure of the benefit. I really like to keep up to date, but my first shot at migrating us to 4rc1 didn't go too well. I will try again. When do you foresee a final 4?

httpete avatar Jul 07 '22 13:07 httpete

When do you foresee a final 4?

I will merge #953, #1029, #1051, when they are ready, and release rc.2. If we don't get any issues with it, we will finalize it.

dai-shi avatar Jul 07 '22 14:07 dai-shi

Thank you @dai-shi I will try right away and provide feedback.

httpete avatar Jul 07 '22 17:07 httpete

I am not sure of the benefit.

You can see the diff of middlewareTypes.test.tsx between v3.6.9 and v4. Here's an excerpt...

Before (v3.6.9):

const useStore = create<
  CounterState,
  SetState<CounterState>,
  GetState<CounterState>,
  StoreApiWithSubscribeWithSelector<CounterState> &
    StoreApiWithPersist<CounterState> &
    StoreApiWithDevtools<CounterState>
>(
  devtools(
    subscribeWithSelector(
      persist(
        (set, get) => ({
          count: 0,
          inc: () => set({ count: get().count + 1 }, false),
        }),
        { name: 'count' }
      )
    ),
    { name: 'prefix' }
  )
)

After (v4.0.0-rc1):

const useStore = create<CounterState>()(
  devtools(
    subscribeWithSelector(
      persist(
        (set, get) => ({
          count: 0,
          inc: () => set({ count: get().count + 1 }, false),
        }),
        { name: 'count' }
      )
    ),
    { name: 'prefix' }
  )
)

my first shot at migrating us to 4rc1 didn't go too well.

Btw there's a migration guide if you missed it. If you follow it you will have mostly no problems. And there is no breaking change in the runtime code whatsoever.

Also looks like you were not using TypeScript for your mocking code or else the bug would have been caught. For instance you were writing create()(...) ie passing undefined to create but not handling it.

I will merge https://github.com/pmndrs/zustand/pull/953, https://github.com/pmndrs/zustand/pull/1029, https://github.com/pmndrs/zustand/pull/1051, when they are ready, and release rc.2.

Let's also wait for my PR to fix #1046, I'll open soon.

devanshj avatar Jul 08 '22 12:07 devanshj

Let's also wait for my PR to fix #1046, I'll open soon.

@devanshj Is it coming soon? Everything else is ready, so I can release rc.2.

dai-shi avatar Jul 16 '22 23:07 dai-shi

The PR was for option 2, but now that we went with option 1, we don't need it and #1090 suffices.

devanshj avatar Jul 17 '22 08:07 devanshj

Cool. Thanks.

dai-shi avatar Jul 17 '22 12:07 dai-shi

Thanks all, as long as the docs that have that snip of "how to mock zustand" is updated when you release, I don't think others will be tripped on this.

https://github.com/pmndrs/zustand/wiki/Testing

httpete avatar Jul 23 '22 14:07 httpete

We want to create new testing guide #968, but it's stalled. It will not be done soon. Could you update the wiki page as much as you can?

dai-shi avatar Jul 23 '22 22:07 dai-shi

Hm, not sure, but does this help?

const store = actualCreate(createState || () => ({}));

It doesn't. I am finding now that Zustand 4 works perfectly in the browser, but my zustand mock isn't resetting properly. I am trying, with the ideas from @devanshj to accommodate the currying but I haven't found it yet.

httpete avatar Jul 27 '22 19:07 httpete

I had to give up, I actually determined that I don't need (or want) because all of our components are wrapped in a unique ZustandContext and instantiated fresh with each test. I don't have the problem of a global state that needs resetting. So I am punting.

I reviewed the testing guide, but as you say it is raw. The barest min would at least have the Testing Wiki have a starter snip with something that works with Z4.

httpete avatar Jul 28 '22 00:07 httpete

@dai-shi I try to mirgration from v3 to v4, but failed too…

How to fix the curried usage?

const create = f =>
  f === undefined ? createImpl : createImpl(f)

const createImpl = createState => {
  const store = actualCreate(createState)
  const initialState = store().getState()
  storeResetFns.add(() => store.setState(initialState, true))

  return store
}

I have tried this but didn't work.

arvinxx avatar Aug 07 '22 14:08 arvinxx

How to fix the curried usage?

Hm, it has to be something like this?

const create = () => createState => {
  const store = actualCreate(createState)
  const initialState = store.getState()
  storeResetFns.add(() => store.setState(initialState, true))
  return store
}

I reviewed the testing guide, but as you say it is raw. The barest min would at least have the Testing Wiki have a starter snip with something that works with Z4.

We need someone to help fixing the doc.

dai-shi avatar Aug 07 '22 22:08 dai-shi

How to fix the curried usage?

Hm, it has to be something like this?

const create = () => createState => {
  const store = actualCreate(createState)
  const initialState = store.getState()
  storeResetFns.add(() => store.setState(initialState, true))
  return store
}

I reviewed the testing guide, but as you say it is raw. The barest min would at least have the Testing Wiki have a starter snip with something that works with Z4.

We need someone to help fixing the doc.

don't work with createContext usage.

arvinxx avatar Aug 15 '22 12:08 arvinxx

don't work with createContext usage.

I'm not familiar with testing with React context, but do people inject store with context, don't they?

dai-shi avatar Aug 17 '22 12:08 dai-shi

I'm getting the same error as @httpete when using import actualCreate from 'zustand' inspite of us using jest.

If I use const actualCreate = jest.requireActual('zustand') // if using jest like the docs mention I end up with TypeError: actualCreate is not a function

razzeee avatar Aug 29 '22 10:08 razzeee

I had the same error as @httpete and @razzeee, but with the change @dai-shi suggested in his previous comment, it's working fine now.

Im using Jest and my file is called zustand.ts (not .js, unlike the docs suggest) and it is located in the __mocks__ folder (at the root of my tests folder, where I've put all my tests). The content is:

import { act } from 'react-dom/test-utils';
import actualCreate from 'zustand';

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

// 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
beforeEach(() => {
  act(() => storeResetFns.forEach((resetFn) => resetFn()));
});

export default create;

I think the docs need an update...

EDIT: I also changed Set to Set<() => void> to avoid typescript error when using resetFn.

gfox1984 avatar Aug 30 '22 14:08 gfox1984

How to fix the curried usage?

Hm, it has to be something like this?

const create = () => createState => {
  const store = actualCreate(createState)
  const initialState = store.getState()
  storeResetFns.add(() => store.setState(initialState, true))
  return store
}

I reviewed the testing guide, but as you say it is raw. The barest min would at least have the Testing Wiki have a starter snip with something that works with Z4.

We need someone to help fixing the doc.

don't work with createContext usage.

I have fix the test by export useStore

import actualCreate from 'zustand';
import { act } from '@testing-library/react-hooks';

// 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 createImpl = (createState) => {
  console.log(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(() => {
  act(() => storeResetFns.forEach((resetFn) => resetFn()));
});

+ const create = (f) => (f === undefined ? createImpl : createImpl(f));

export default create;

+ export { useStore } from 'zustand';

arvinxx avatar Sep 09 '22 15:09 arvinxx

Hey, I had the same problem than OP and I fixed with the following: The difference is, instead of const create = createState => {...} it should be: const create = () => createState => {...}.

How to fix the curried usage?

Hm, it has to be something like this?

const create = () => createState => {
  const store = actualCreate(createState)
  const initialState = store.getState()
  storeResetFns.add(() => store.setState(initialState, true))
  return store
}

I reviewed the testing guide, but as you say it is raw. The barest min would at least have the Testing Wiki have a starter snip with something that works with Z4.

We need someone to help fixing the doc.

Thanks!

tfreitas90 avatar Sep 27 '22 09:09 tfreitas90

Hi, hit the same issue and resolved it by migrating my project to jotai.

kandrelczyk avatar Jan 31 '23 19:01 kandrelczyk

I believe this issue is resolved now with const create = () => createState => {...}, as per this comment, so I'll close it now.

For reference, I'll put the relevant docs links:

If a similar case happens in the future, this issue can be re-opened or, better yet, referenced.

sewera avatar Feb 16 '23 12:02 sewera

I know this is an older+closed issue, but for others struggling to get mock-resets to work properly I still want to mention that what @devanshj said in https://github.com/pmndrs/zustand/issues/1059#issuecomment-1175974777 was necessary for us since we use middleware and sometimes the curried, sometimes the regular invocation.

We're using ts/jest/TLR and saw great results like so:

import { act } from '@testing-library/react'
import { create as createType, StateCreator } from 'zustand'

const zustand = jest.requireActual('zustand')
const actualCreate: typeof createType = zustand.create

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

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

// support currying
export function create<S>(f: StateCreator<S>) {
  return f === undefined ? createImpl : createImpl(f)
}

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

Note that the docs still tell users to only support the uncurried version, which might cause trouble for some (like us).

tom2strobl avatar Mar 13 '23 12:03 tom2strobl