3

I'm following the Zustand wiki to implement testing, but the provided solution is not working for a basic test for app rendering. My project is built on top of the Electron React Boilerplate boilerplate project.

Here's the full error. Jest is using node with experimental-vm-modules because I followed the the Jest docs to support ESM modules.

$ cross-env NODE_OPTIONS=--experimental-vm-modules jest
(node:85003) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
jest-haste-map: Haste module naming collision: myproject
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/package.json
    * <rootDir>/src/package.json

 FAIL  src/__tests__/App.test.tsx
  ● Test suite failed to run

    TypeError: create is not a function

      12 | }
      13 |
    > 14 | const useNotifs = create<NotifsState>(
         |                   ^
      15 |   // devtools(
      16 |   (set) => ({
      17 |     notifStore: notifsDefault.notifStore,

      at src/state/notifs.ts:14:19
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:387:19)
      at _run10000 (node_modules/@jest/core/build/cli/index.js:408:7)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        11.882 s
Ran all test suites.
error Command failed with exit code 1.

At the top of the notifs.ts file, Zustand is imported normally with import create from 'zustand'.

Jest config in package.json:

    ...
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/config/mocks/fileMock.js",
      "\\.(css|less|sass|scss)$": "identity-obj-proxy",
      "zustand": "<rootDir>/src/__mocks__/zustand.js",
    },
    "transformIgnorePatterns": [
      "node_modules/(?!(zustand)/)",
      "<rootDir>/src/node_modules/"
    ],
    "moduleDirectories": [
      "node_modules",
      "src/node_modules"
    ],
    "moduleFileExtensions": [
      "js",
      "jsx",
      "ts",
      "tsx",
      "json"
    ],
    "moduleDirectories": [
      "node_modules",
      "src/node_modules"
    ],
    "extensionsToTreatAsEsm": [
      ".ts",
      ".tsx"
    ],

    ...

I have left the ./src/__mocks__/zustand.js file exactly the same as from the Zustand wiki Testing page. I receive the same error whether or not I have zustand in the transformIgnorePatterns.

My Babel configuration includes [require('@babel/plugin-proposal-class-properties'), { loose: true }], in the plugins section, and output.library.type is 'commonjs2'

My tsconfig.json has compilerOptions.module set to "CommonJS", and the project's package.json "type" field is set to "commonjs".

Dependency versions:

    "@babel/core": "^7.12.9",
    "@babel/preset-env": "^7.12.7",
    "@babel/preset-react": "^7.12.7",
    "@babel/preset-typescript": "^7.12.7",
    "@babel/register": "^7.12.1",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "@testing-library/jest-dom": "^5.11.6",
    "@testing-library/react": "^11.2.2",
    "babel-jest": "^27.0.6",
    "babel-loader": "^8.2.2",
    "jest": "^27.0.6",
    "regenerator-runtime": "^0.13.9",
    "source-map-support": "^0.5.19",
    "typescript": "^4.0.5",
    "webpack": "^5.5.1",
    "zustand": "^3.5.5"

I don't know what else could be relevant, just let me know if anything else is needed. Any and all help appreciated, thanks for your time.

fowl-
  • 41
  • 1
  • 4

4 Answers4

4

To do this you should use the actual store of your app

const initialStoreState = useStore.getState()
 
 beforeEach(() => {
    useStore.setState(initialStoreState, true)
  })

useStore.setState({ me: memberMockData, isAdmin: true })

The documentation seems off. So don't follow it.

Bon Andre Opina
  • 2,129
  • 2
  • 15
  • 33
  • I was importing the same store used in the app, the line: `const useNotifs = create(` Is from the store file, the `create` function is from `zustand`. – fowl- May 07 '22 at 16:33
  • 1
    Much simpler than the official docs. Works like a charm! – pom421 Apr 25 '23 at 15:12
1

Looks similar to the error I came across:

    TypeError: store.getState is not a function

   9 | export const create = <S>(createState: StateCreator<S>) => {
  10 |      const store = actualCreate(createState);
> 11 |      const initialState = store.getState();
     |                                 ^
  12 |      storeResetFns.add(() => store.setState(initialState, true));
  13 |      return store;
  14 | };

In my case, it was caused by not currying the create function like so:

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

See this issue.

grigol
  • 11
  • 1
0

use jest 28.0.0-alpha.0 will simply resolve the issue. I think the problem is zustand uses '.mjs' as the entry point.

Himself65
  • 1
  • 1
  • Dont do this it destroyed my node and test files. – Bon Andre Opina Apr 12 '22 at 04:47
  • I'm afraid I'm no longer working on this project so I can't test this solution, and because of @BonAndreOpina 's comment I'm hesitant to accept this. I wish the best of luck on anyone still trying to use Zustand with Jest. – fowl- May 07 '22 at 16:56
0

Add this code to zustand.ts in mocks folder in order to mock Zustand.

__mocks__/zustand.ts

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

// a variable to hold reset functions for all stores declared in the app
const storeResetFns = new Set();
// @ts-ignore
const extension = {
  subscribe: jest.fn(() => {
    return () => {};
  }),
  unsubscribe: jest.fn(),
  send: jest.fn(),
  init: jest.fn(),
  error: jest.fn(),
};
const extensionConnector = { connect: jest.fn(() => extension) };
(window as any).__REDUX_DEVTOOLS_EXTENSION__ = extensionConnector;

// when creating a store, we get its initial state, create a reset function and add it in the set
const create = (createState: any) => {
  const store = actualCreate<any>(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: any) => resetFn()));
});

export { create };

Make sure you do named export and not default for custom create method.

Kumar Parth
  • 439
  • 5
  • 7