10

hej, maybe someone can help me with this? I try to mock a zustand store with custom values and actions. I'd like to do the mocking within my test file. But I fail miserably.

This is a simplified example of my react app.

component.jsx

import React from "react";
import useStore from "@app/component_store";

export default function Component() {
  const foo = useStore((state) => state.foo);
  const setFoo = useStore((state) => state.foo_set);

  return (
    <>
      <div data-testid="foo">{foo}</div>

      <button data-testid="btn-foo" onClick={() => setFoo("new foo")}>
        set foo
      </button>
    </>
  );
}

component_store.jsx

import create from "zustand";

const useStore = create((set) => ({
  foo: "foo",
  foo_set: (value) => set(() => ({ foo: value })),
}));
export default useStore;

component.test.jsx

/* global  afterEach, expect, jest, test  */
import React from "react";
import { cleanup, fireEvent, render } from "@testing-library/react";
import "@testing-library/jest-dom";
import mockCreate from "zustand";

import Component from "@app/component";

jest.mock("@app/component_store", () => (args) => {
  const useStore = mockCreate((set, get) => ({
    foo: "mocked foo",
    foo_set: (value) => set(() => ({ foo: "mocked set foo" })),
  }));
  return useStore(args);
});

afterEach(cleanup);

test("override store values and actions", () => {
  const { container, getByTestId } = render(<Component />);

  const foo = getByTestId("foo");
  const btnFoo = getByTestId("btn-foo");

  expect(foo.textContent).toBe("mocked foo");

  fireEvent.click(btnFoo);
  expect(foo.textContent).toBe("mocked set foo");
});

This fails. expect(foo.textContent).toBe("mocked set foo"); will still have "mocked foo" as a value, although the mocked foo_set gets called (e.g. adding a console log does get logged). But again, the value of foo will not be updated.

However, if I move the store mocking part into its own file and import it into my test, everything will work as expected.

component_store_mocked.jsx

note: this is the same as in component.test.jsx

import create from "zustand";

const useStore = create((set, get) => ({
  foo: "mocked foo",
  foo_set: (value) => set(() => ({ foo: "mocked set foo" })),
}));
export default useStore;

updated component.test.jsx

/* global  afterEach, expect, jest, test  */
import React from "react";
import { cleanup, fireEvent, render } from "@testing-library/react";
import "@testing-library/jest-dom";
import mockCreate from "zustand";

import Component from "@app/component";

// this part here
import mockComponentStore from "@app/component_store_mocked";

jest.mock("@app/component_store", () => (args) => {
  const useStore = mockComponentStore;
  return useStore(args);
});

afterEach(cleanup);

test("override store values and actions", () => {
  const { container, getByTestId } = render(<Component />);

  const foo = getByTestId("foo");
  const btnFoo = getByTestId("btn-foo");

  expect(foo.textContent).toBe("mocked foo");

  fireEvent.click(btnFoo);
  expect(foo.textContent).toBe("mocked set foo");
});

I would love to do the mocking within the test file and not pollute my system with dozens of mocked stores for my test ... Again, this is just a simplified setup of a much larger app.

noob
  • 192
  • 1
  • 11

2 Answers2

0

I had the same problem and I solved it like this:

I'm using React Native so I inserted Zustand's mock to reset the state with each test. https://docs.pmnd.rs/zustand/guides/testing

I created an action in the store called _mock that receives an object and inserts it in the store.

mock action:

_mock(store) {
  set({
    state: { ...get().state, ...store },
    actions: get().actions,
 });
},

mock action type:

_mock: (store: Partial<RootStateAuth>) => void;

Usage:

useAuthStore.getState().actions._mock({
  role: 'purchases',
  registrationCompleted: false,
  name: 'Fulano',
});

I hope it helps.

0

You can mock the data that is returned from the store within the test with a jest mock.

when you call UseWhateverStore within your function you retrieve a function that has the state passed as the argument, then when you say state.foo it is returning that state from the store.

Here is an example of a mock

jest.mock("../stores/accounts-store.ts", () => ({
    useAccountsStore: (passedFunction: any) => {
        const data = {
            createNewAccount: jest.fn(),
            getFactions: jest.fn(),
            factions: []
        }

        return passedFunction(data);
    }
}));
Adear
  • 1,885
  • 2
  • 11
  • 18