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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue while testing component that uses createSelector #489

Open
petermarcoen opened this issue Feb 16, 2021 · 1 comment
Open

Issue while testing component that uses createSelector #489

petermarcoen opened this issue Feb 16, 2021 · 1 comment

Comments

@petermarcoen
Copy link

I have a weird issue which is causing some of my tests to fail.
I am not sure if this is a problem with reselect or with redux-mock-store (or with me)

I created a simplified version of my problem on CodeSandbox: https://codesandbox.io/s/wizardly-wave-nkuvw?file=/src/Component.js

Here is the problem:

I have a component (Component.js) which uses createSelector to get a value from the store:

const itemSelector = (state) => state.items;
const itemForId = createSelector(
  [itemSelector, (state, id) => id],
  (items, id) => items.find((item) => item.id === id)
);

export default function Component() {
  const item = useSelector((state) => itemForId(state, 1));
  return <div>{item.value}</div>;
}

To test this component I created 2 different tests (Component.test.js) in which I use react-mock-store to mock my store.

  • Case 1: Mock item with ID 1 and value 1

  • Case 2: Mock item with ID 1 and value 2

When I create a store by calling mockStore with these items directly everything works as expected.

  it("Case 1 - renders value 1 for item 1 when offering new object to mockStore", async () => {
    const store = mockStore({
      items: [
        {
          id: 1,
          value: "Value 1"
        }
      ]
    });

    render(
      <Provider store={store}>
        <Component />
      </Provider>
    );

    expect(await screen.findByText("Value 1"));
  });

  it("Case 2 - renders value 2 for item 1 when offering new object to mockStore", async () => {
    const store = mockStore({
      items: [
        {
          id: 1,
          value: "Value 2"
        }
      ]
    });

    render(
      <Provider store={store}>
        <Component />
      </Provider>
    );

    expect(await screen.findByText("Value 2"));
  });

Case 1 returns value 1 and Case 2 returns value 2 👍

Now, in my real app I have other parts of my state that I have to mock so I start from a common object (initialState) and append these items to it. I recreated that here as Case 3 and 4:

const initialState = {
    items: []
  };

it("Case 3 - renders value 1 for item 1 when offering copied object to mockStore", async () => {
    let state = initialState;
    state.items = [
      {
        id: 1,
        value: "Value 1"
      }
    ];

    const store = mockStore(state);

    render(
      <Provider store={store}>
        <Component />
      </Provider>
    );

    expect(await screen.findByText("Value 1"));
  });

  it("Case 4 - renders value 2 for item 1 when offering copied object to mockStore", async () => {
    let state = initialState;
    state.items = [
      {
        id: 1,
        value: "Value 2"
      }
    ];

    const store = mockStore(state);

    render(
      <Provider store={store}>
        <Component />
      </Provider>
    );

    expect(await screen.findByText("Value 2"));
  });

Now Case 4 fails because the selector returns the value from Case 3 (value 1).
Some observations:

  • running only the last test does succeed
  • initialState is const so it should not be able to carry over data from Case 3 to Case 4
  • if I print out the state of the failing test before creating the mockStore it show the correct item (value 2)
  • if I skip createSelector and just use useSelector it does work correctly:
const item = useSelector((state) =>
   state.items.find((item) => item.id === 1)
);

This last observation leads me to believe this might be an issue with Reselect.

I would appreciate some help. Thank you.

@yuningjiang123
Copy link

I think it is because, under the hood, reselect will check the equality of your input arguments first, before checking the extracted output arguments.

In Case 1 and 2, the references of the initialState passed into mockStore are different, so the references of state in your useSelector are different, itemForId will recalculate a result based on the differenct state.

In Case 3 and 4, the references of the initialState are the same, therefore itemForId will use the result cached in Case 3 when running in Case 4.

You won't hit such problem if you're using redux because each time you dispatch an action, a root state with a new reference will give birth under the hood.

Your other observations will make sense based on the theory above.

I'm poor at English. Let me know if I have made mistakes.

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

No branches or pull requests

2 participants