Testing connected components with enzyme

后端 未结 2 426
青春惊慌失措
青春惊慌失措 2021-01-27 13:06

I am learning taking this testing course to test connected components by setting up a store factory test helper that \'creates a store for testing that matches the

相关标签:
2条回答
  • 2021-01-27 13:30

    Just a heads up about that Udemy course... it's not the greatest learning tool. The instructor approaches testing using data attributes which are unnecessary for jest and enzyme testing (they also crowd up the DOM with unused attributes).

    In addition, her code experience is around a beginner level and she makes quite a few mistakes and odd code choices. That said, learn what you can from it and start looking into tests created by those who maintain popular npm packages (most well-documented and popular packages will contain tests that'll teach you a more practical approach of unit and integration testing).

    Anyway, I digress, you have two options for testing a container:

    1. export the class/pure function, shallow or mount wrap it, and update it with fake props (very easy, less of a headache, and more common to do)
    2. Wrap your component in a redux <Provider> and react-router-dom's <MemoryRouter>, and then mount it (can become very complex as it requires a semi-deep understanding of: enzyme and how it interprets the DOM when a component is mounted, redux's action/reducer flow, how to create mock implementations and/or mock files, and how to properly handle promise based actions).

    Working examples (click the Tests tab to run the tests; locate the .tests.js in the directories mentioned below):


    Note: Codesandbox currently has some testing limitations as noted below, so please adjust for your local project.

    containers/Dashboard/__tests__/UnconnectedDashboard.test.js (you can just as easily mount wrap this unconnected component to assert against its deeply nested children nodes)

    import { Dashboard } from "../index.js";
    
    /* 
       codesandbox doesn't currently support mocking, so it's making real
       calls to the API; as a result, the lifecycle methods have been
       disabled to prevent this, and that's why I'm manually calling
       componentDidMount.
    */
    
    const getCurrentProfile = jest.fn();
    
    const fakeUser = {
      id: 1,
      name: "Leanne Graham",
      username: "Bret",
      email: "Sincere@april.biz",
      address: {
        street: "Kulas Light",
        suite: "Apt. 556",
        city: "Gwenborough",
        zipcode: "92998-3874",
        geo: {
          lat: "-37.3159",
          lng: "81.1496"
        }
      },
      phone: "1-770-736-8031 x56442",
      website: "hildegard.org",
      company: {
        name: "Romaguera-Crona",
        catchPhrase: "Multi-layered client-server neural-net",
        bs: "harness real-time e-markets"
      }
    };
    
    const initialProps = {
      getCurrentProfile,
      currentUser: {},
      isLoading: true
    };
    
    describe("Unconnected Dashboard Component", () => {
      let wrapper;
      beforeEach(() => {
        wrapper = shallow(<Dashboard {...initialProps} />);
        wrapper.instance().componentDidMount();
      });
    
      afterEach(() => wrapper.unmount());
    
      it("initially renders a spinnner", () => {
        expect(getCurrentProfile).toHaveBeenCalled();
        expect(wrapper.find("Spinner")).toHaveLength(1);
      });
    
      it("displays the current user", () => {
        wrapper.setProps({ currentUser: fakeUser, isLoading: false });
        expect(getCurrentProfile).toHaveBeenCalled();
        expect(wrapper.find("DisplayUser")).toHaveLength(1);
      });
    
      it("displays a signup message if no users exist", () => {
        wrapper.setProps({ isLoading: false });
        expect(getCurrentProfile).toHaveBeenCalled();
        expect(wrapper.find("DisplaySignUp")).toHaveLength(1);
      });
    });
    

    containers/Dashboard/__tests__/ConnectedDashboard.test.js

    import Dashboard from "../index";
    // import { getCurrentProfile } from "../../../actions/profileActions";
    import * as types from "../../../types";
    
    /* 
      codesandbox doesn't currently support mocking, so it's making real
      calls to the API; however, actions like getCurrentProfile, should be
      mocked as shown below -- in your case, you wouldn't need to use
      a promise, but instead just mock the "guessedWord" action and return
      store.dispatch({ ... })
    */
    
    const fakeUser = {
      id: 1,
      name: "Leanne Graham",
      username: "Bret",
      email: "Sincere@april.biz",
      address: {
        street: "Kulas Light",
        suite: "Apt. 556",
        city: "Gwenborough",
        zipcode: "92998-3874",
        geo: {
          lat: "-37.3159",
          lng: "81.1496"
        }
      },
      phone: "1-770-736-8031 x56442",
      website: "hildegard.org",
      company: {
        name: "Romaguera-Crona",
        catchPhrase: "Multi-layered client-server neural-net",
        bs: "harness real-time e-markets"
      }
    };
    
    const flushPromises = () => new Promise(resolve => setImmediate(resolve));
    
    describe("Connected Dashboard Component", () => {
      let store;
      let wrapper;
      beforeEach(() => {
        store = createStoreFactory();
        wrapper = mount(
          <Provider store={store}>
            <MemoryRouter>
              <Dashboard />
            </MemoryRouter>
          </Provider>
        );
      });
    
      afterEach(() => wrapper.unmount());
    
      it("initially displays a spinner", () => {
        expect(wrapper.find("Spinner")).toHaveLength(1);
      });
    
      it("displays the current user after a successful API call", async () => {
        /* 
          getCurrentProfile.mockImplementationOnce(() => new Promise(resolve => {
            resolve(
              store.dispatch({
                type: types.SET_SIGNEDIN_USER,
                payload: fakeUser
              })
            );
          });
    
          await flushPromises();
          wrapper.update();
    
          expect(wrapper.find("DisplayUser")).toHaveLength(1);
        */
    
        store.dispatch({
          type: types.SET_SIGNEDIN_USER,
          payload: fakeUser
        });
    
        wrapper.update();
    
        expect(wrapper.find("DisplayUser")).toHaveLength(1);
      });
    
      it("displays a signup message if no users exist", async () => {
        /* 
          getCurrentProfile.mockImplementationOnce(() => new Promise((resolve,reject) => {
            reject(
              store.dispatch({
                type: types.FAILED_SIGNEDIN_USER
              })
            );
          });
    
          await flushPromises();
          wrapper.update();
    
          expect(wrapper.find("DisplaySignUp")).toHaveLength(1);
        */
    
        store.dispatch({
          type: types.FAILED_SIGNEDIN_USER
        });
    
        wrapper.update();
    
        expect(wrapper.find("DisplaySignUp")).toHaveLength(1);
      });
    });
    
    0 讨论(0)
  • 2021-01-27 13:32

    Solution: I forgot the browser parameter for my root reducer, given I was using connected-react-router.

    import rootReducer from 'reducers/rootReducer';
    import {createBrowserHistory} from 'history';
    
    
    export const storeFactory = (initialState) => createStore(rootReducer(createBrowserHistory()), initialState);
    
    0 讨论(0)
提交回复
热议问题