使用 Jest 测试 class 组件 firebase 操作

Test class component firebase action with Jest

我是开玩笑的初学者,我尝试用开玩笑测试 firebase 插件。如果我有一个名为 Orders 的 class 组件并将 firebase 操作方法连接为一个名为 fetchOrders 的道具。 这是我的设置,

  1. OrderAction.js

    import { app } from "firebase";
    import {
     DATABASE_COLLECTION_ORDERS,
    } from "../../config";
    
    export const fetchOrders = () => async (dispatch) =>
    {
     const objList = [];
    
     app().firestore().collection(DATABASE_COLLECTION_ORDERS).get()
         .then((snapShot) =>
         {
             snapShot.forEach((doc) =>
             {
                 if (doc.exists)
                 {
                     // get the collection primary id
                     const { id } = doc;
                     // read the data
                     const data = doc.data();
    
                     objList.push({ ...data, id });
                 }
             });
    
             dispatch({
                 type    : ORDER_APPEND,
                 payload : objList,
             });
         })
         .catch((err) =>
         {
             reportError(err);
         });
    };
    
  2. Orders.js

    import React from "react";
    import { fetchOrders, fetchOrderItems } from "../../redux/action/OrderAction";
    
    class Orders extends React.Component{
       async componentDidMount()
       {
          this.props.fetchOrders();
       }
       render()
       {
           return (
                ...content
           )
       }
    }
    
    const mapStateToProps = (state) => ({
       orders   : state.orders,
    });
    
    export default connect(mapStateToProps,
    { 
        fetchOrders,
    })(Orders);
    
  3. Orders.test.js

     import React from 'react';
     import { configure, mount } from 'enzyme';
     import Adapter from 'enzyme-adapter-react-16';
     import { Provider } from 'react-redux';
     import configEnzyme from "../setupTest";
     import store from "../redux/store";
     import Orders from "../views/basic/Orders";
    
     describe('Test case for testing orders', () =>
     {
         // configure the jtest
         configure({ adapter: new Adapter() });
    
         // mount login component to the wrapper
         let wrapper;
    
         const originalWarn = console.warn;
    
         // console.warn = jest.fn();
    
         // disable the console warnings
         beforeEach(() =>
         {
             // jest.spyOn(console, 'warn').mockImplementation(() => {});
             // jest.spyOn(console, 'error').mockImplementation(() => {});
    
             wrapper = mount(
                 <Provider store={store}>
                     <Orders />
                 </Provider>,
             );
         });
    
         // test case fetching data
         it('check input data', () =>
         {
             const componentInstance = wrapper.find('Orders').instance();
    
             componentInstance.props.fetchOrders();
    
             // wait for 2 seconds for redux store update
             jest.useFakeTimers();
             setTimeout(() =>
             {
                wrapper.update();
    
                expect(componentInstance.props.orderHeader).not.toBeNull();
             }, 2000);
         });
     });
    

然后我运行测试用例,这里是结果

(node:24100) UnhandledPromiseRejectionWarning: FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app). (node:24100) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To t erminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) (node:24100) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. (node:24100) UnhandledPromiseRejectionWarning: FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created

  • call Firebase App.initializeApp() (app/no-app). (node:24100) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To t erminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 3) (node:24100) UnhandledPromiseRejectionWarning: FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created
  • call Firebase App.initializeApp() (app/no-app). (node:24100) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To t erminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 4) PASS src/test/Orders.test.js (8.099s)

有解决办法吗?

发生这种情况是因为在您的测试环境中您没有启动 firebase 应用程序(使用 initializeApp)而且您可能不应该这样做(您不想每次都与 firebase 通信 运行 一个测试,尤其不是单元测试)。 这种问题也适用于其他外部服务,例如您自己的服务器和外部 API。

那么如何测试您的应用程序?

答案是模拟 - 为测试环境提供这些服务的替代实现。有某种模拟,取决于你想模拟什么以及如何模拟。此外,有时,这些工具会提供自己的测试包(再次为其公开的方法提供可测试的实现)。

在这种情况下,您可以使用 jest 模拟机制来模拟来自 firebase 的响应,因此您的应用“不知道”它从其他资源接收到数据,并且会像它应该的那样运行。

相关的开玩笑方法是spyOnmockImplementation,这里是一个例子(我简化了你的组件):

App.spec.js

test("mount", async () => {
  const fetchPromise = Promise.resolve([{ name: "order1" }]);
  jest.spyOn(firebase, "app").mockImplementation(() => ({
    firestore: () => ({
      collection: () => ({
        get: () => fetchPromise
      })
    })
  }));
  let wrapper = mount(<App />);
  await fetchPromise;
  wrapper.update();
  expect(wrapper.find("span").text()).toBe("order1");
});

App.js

export default class App extends React.Component {
  state = {
    orders: []
  };

  fetchOrders() {
    try {
      app()
        .firestore()
        .collection(DATABASE_COLLECTION_ORDERS)
        .get()
        .then((snapShot) => {
          this.setState({ orders: snapShot });
        });
    } catch {
      console.log("do nothing");
    }
  }

  componentDidMount() {
    this.fetchOrders();
  }

  render() {
    return (
      <div className="App">
        {this.state.orders.map((order) => (
          <span key={order.name}>{order.name}</span>
        ))}
      </div>
    );
  }
}

https://codesandbox.io/s/enzyme-setup-in-codesandbox-forked-jyij5?file=/src/App.js:133-730(单击测试 选项卡)