atlassian / react-sweet-state

Shared state management solution for React
https://atlassian.github.io/react-sweet-state/
MIT License
871 stars 55 forks source link

How to test if a call to an action forces component re-render. #93

Closed akexinad closed 4 years ago

akexinad commented 4 years ago

There isn't much documentation on how to test a particular action call and see how it affects the way the component renders.

Example:

Below I have a Fizz.tsx component that calls an action inside useEffect:

export const Fizz = () => {
  const [state, actions] = useBuzzStore();

  useEffect(() => {
    actions.changeFoo(100);
  }, [actions]);

  return (
    <div>
      {state.foo > 0 ? <h2>Foo is {state.foo}</h2> : null}
    </div>
  );
};

This is my store:

import { createStore, createHook, StoreActionApi } from "react-sweet-state";

interface State {
  foo: number;
}

type StoreApi = StoreActionApi<State>;

export const initialState: State = {
  foo: 0
};

export const actions = {
  changeFoo: (num: number) => ({ getState, setState }: StoreApi) => {
    setState({
      foo: isNaN(+num) ? 0 : num
    });
  }
};

type Actions = typeof actions;

const Store = createStore<State, Actions>({
  initialState,
  actions,
  name: "buzzStore"
});

export const useBuzzStore = createHook(Store);

Basically if state.foo is greater than zero, the component renders an h2 with state.foo's value. If it's zero it renders nothing.

How would test this within jest and enzyme in a way where I can invoke the action from within the test and then write an assertion like:

describe("the fizz component", () => {
  let wrapper: ShallowWrapper;

  beforeEach(() => {
    wrapper = shallow(<Fizz />);
  });

  afterEach(() => {
    wrapper.unmount();
  });

  it("should not render an h2", () => {
    // NO CALL to action.changeFoo();

    expect(wrapper.find("h2").exists()).toBeFalsy();

    // mock the action for action.changeFoo(mockValue)
  });

  it("should render an h2", () => {
    const numberForFoo = 100;

    // CALL action.changeFoo(numberForFoo)

    expect(wrapper.find("h2").text()).toContain("Foo is " + numberForFoo);
  });
});

Link to the codesandbox: https://codesandbox.io/s/zen-greider-hohdt

albertogasparin commented 4 years ago

Sorry for the late reply. You are trying to create a sort of integration test, so I suggest you follow best practices by mounting the component and observing the side effects instead of trying to get the actions and manually trigger them on a shallow wrapper.

Another thing I you could do is using dependency replacement to test Fizz in isolation, independently from the actual useBuzzStore implementation: have a look at https://github.com/albertogasparin/react-magnetic-di as a nice solution to achieve that