使用 Jest 测试 class 组件 firebase 操作
Test class component firebase action with Jest
我是开玩笑的初学者,我尝试用开玩笑测试 firebase 插件。如果我有一个名为 Orders
的 class 组件并将 firebase 操作方法连接为一个名为 fetchOrders
的道具。
这是我的设置,
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);
});
};
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);
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 的响应,因此您的应用“不知道”它从其他资源接收到数据,并且会像它应该的那样运行。
相关的开玩笑方法是spyOn
和mockImplementation
,这里是一个例子(我简化了你的组件):
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(单击测试 选项卡)
我是开玩笑的初学者,我尝试用开玩笑测试 firebase 插件。如果我有一个名为 Orders
的 class 组件并将 firebase 操作方法连接为一个名为 fetchOrders
的道具。
这是我的设置,
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); }); };
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);
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 的响应,因此您的应用“不知道”它从其他资源接收到数据,并且会像它应该的那样运行。
相关的开玩笑方法是spyOn
和mockImplementation
,这里是一个例子(我简化了你的组件):
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(单击测试 选项卡)