首页 > 解决方案 > 使用 saga 对组件进行 React 测试

问题描述

大家好,我在测试我的组件时遇到了一些麻烦

问题是我想测试我的React Native 组件,它使用saga从服务器获取数据。

问题是我确实知道我应该做什么,我想我应该在我的测试文件中模拟我的API调用,但我不知道如何:/

组件文件非常简单,安装后它会调度操作以获取车辆列表,然后在 UI 中显示它们。在获取之前,它会显示加载文本

下面是我当前的组件和测试文件设置。

这是一个在屏幕加载时获取初始数据的屏幕组件

屏幕组件

import React, { useContext, useEffect, useState } from 'react';
import { Platform, FlatList, View, ActivityIndicator, Text } from 'react-native';
import PropTypes from 'prop-types';

import { useDispatch, useSelector } from 'react-redux';
import { vehiclesActions } from '_store/vehicles';


export const MainScreen = ({ navigation }) => {


  /**
   * Redux selectors and dispatch
   */
  const {
    loading = true,
    vehicles = [],
    loadMore = false
  } = useSelector((state) => state.vehicles);


  /**
 * Initial effect, fetches all vehicles
 */
  useEffect(() => {
    dispatch(
      vehiclesActions.vehicleGet({
        page: 1,
      })
    );
  }, []);

  const renderCard = () => {
    return (<View><Text>Test</Text></View>)
  }


 if (loading) {
     return (<View><Text>App Loading </Text></View>
  }

  return (
    <View style={styles.wrapper}>
      <View
        style={
          Platform.OS === 'ios' ? { marginTop: 30 } : { marginTop: 0, flex: 1 }
        }
      >
        {!loading && (
          <View style={Platform.OS === 'ios' ? {} : { flex: 1 }}>
            <FlatList
              testID={'flat-list'}
              data={vehicles}
              renderItem={renderCard}
            />
          </View>
        )}
      </View>
    </View>
  );
};

MainScreen.propTypes = {
  navigation: PropTypes.object
};

export default MainScreen;

我的车辆传奇:

const api = {
  vehicles: {
    getVehicles: (page) => {
      return api.get(`/vehicles/list?page=${page}`, {});
    },
}
function* getVehicles(action) {
  try {
    const { page } = action.payload;
    const { data } = yield call(api.vehicles.getVehicles, page);
    yield put({ type: vehiclesConstants.VEHICLE_GET_SUCCESS, payload: data });

  } catch (err) {
    yield call(errorHandler, err);
    yield put({ type: vehiclesConstants.VEHICLE_GET_FAIL });
  }
}

export function* vehiclesSaga() {
  yield takeLatest(vehiclesConstants.VEHICLE_GET_REQUEST, getVehicles);
}

行动:

export const vehiclesActions = {
  vehicleGet: payload => ({ type: vehiclesConstants.VEHICLE_GET_REQUEST, payload }),
  vehicleGetSuccess: payload => ({ type: vehiclesConstants.VEHICLE_GET_SUCCESS, payload }),
  vehicleGetFail: error => ({ type: vehiclesConstants.VEHICLE_GET_FAIL, error }),
}

减速器

import { vehiclesConstants } from "./constants";

const initialState = {
  vehicles: [],
  loading: true,
};

export const vehiclesReducer = (state = initialState, action) => {
  switch (action.type) {
    case vehiclesConstants.VEHICLE_GET_REQUEST:
       return {
        ...state,
        loading: true,
      };
    case vehiclesConstants.VEHICLE_GET_SUCCESS:
      return {
        ...state,
        loading: false,
        vehicles: action.payload,
      };
  }
}

我的测试文件

import 'react-native';
import React from 'react';

import {cleanup, render, fireEvent} from '@testing-library/react-native';

import AppScreen from '../../../../src/screens/App/index';

import {Provider} from 'react-redux';
import {store} from '../../../../src/store/configureStore';

describe('App List Component', () => {
  beforeEach(() => jest.useFakeTimers());
  afterEach(cleanup);

  it('should render vehicle list page title', async () => {

    const navigation = {
      setParams: () => {},
      navigate: jest.fn(),
    };

    const route = {

    }
    const component = (
      <Provider store={store}>
        <AppScreen route={route} navigation={navigation} />
      </Provider>);

    const {getByText, getByTestId} = render(component);
    const pageTitle = await getByText('App Loading'); // this works fine
    expect(pageTitle).toBeDefined();
  });

  it('should navigate to add vehicle', async () => {

    const navigation = {
      setParams: () => {},
      navigate: jest.fn(),
    };

    const route = {

    }
    const component = (
      <Provider store={store}>
        <AppScreen route={route} navigation={navigation} />
      </Provider>);

    const {getByText, getByTestId} = render(component);
    const flatList = await getByTestId('flat-list');// this throws error since flat list is still not shown, and loading is showing instead
  });

就像我在上面看到的那样,我找不到带有 testId flat-list 的元素,因为组件AppScreen它总是显示加载文本,有什么方法可以模拟该 API 调用并使其工作吗?

标签: javascriptreactjsreact-nativejestjs

解决方案


Jest 允许您使用jest.mock.

你必须写一个axios.get像这样的替代方案


const vehiclesData = [
// ... put default data here
]

const delay = (ms, value) =>
  new Promise(res => setTimeout(() => res(value), ms))

const mockAxiosGet = async (path) => {
  let result = null
  if (path.includes('vehicles/list') {
    const query = new URLSearchParams(path.replace(/^[^?]+\?/, ''))
    const page = + query.get('page')
    const pageSize = 10
    const offset = (page - 1)*pageSize
    result = vehiclesData.slice(offset, offset + pageSize)
  }
  return delay(
    // simulate 100-500ms latency
    Math.floor(100 + Math.random()*400),
    { data: result }
  )
}

然后将测试文件修改为

import 'react-native';
import React from 'react';

import {cleanup, render, fireEvent} from '@testing-library/react-native';
import axios from 'axios'

// enable jest mock on 'axios' module
jest.mock('axios')

import AppScreen from '../../../../src/screens/App/index';

import {Provider} from 'react-redux';
import {store} from '../../../../src/store/configureStore';

describe('App List Component', () => {
  before(() => {
    // mock axios implementation
    axios.get.mockImplementation(mockAxiosGet)
  })
  beforeEach(() => jest.useFakeTimers());
  afterEach(cleanup);

  it('should render vehicle list page title', async () => {

    const navigation = {
      setParams: () => {},
      navigate: jest.fn(),
    };

    const route = {

    }
    const component = (
      <Provider store={store}>
        <AppScreen route={route} navigation={navigation} />
      </Provider>);

    const {getByText, getByTestId} = render(component);
    const pageTitle = await getByText('App Loading'); // this works fine
    expect(pageTitle).toBeDefined();
  });

  it('should navigate to add vehicle', async () => {

    const navigation = {
      setParams: () => {},
      navigate: jest.fn(),
    };

    const route = {

    }
    const component = (
      <Provider store={store}>
        <AppScreen route={route} navigation={navigation} />
      </Provider>);

    const {getByText, getByTestId} = render(component);
    const flatList = await getByTestId('flat-list');// this throws error since flat list is still not shown, and loading is showing instead
  });

对于您的用例,请在Mocking Implementations阅读更多内容


推荐阅读