0

我正在使用 React Native 和 Redux 编写应用程序。我正在设计一个登录表单并想测试组件处理提交功能。在handleSubmit()函数中,应该将几个操作分派给 Redux。让我给你handleSubmit()函数代码和它的测试。首先是函数本身:

handleSubmit = (values, formikBag) => {
  formikBag.setSubmitting(true);
  const { loginSuccess, navigation, setHouses, setCitizens } = this.props;
  apiLoginUser(values.email, values.password)
    .then(data => {
      const camelizedJson = camelizeKeys(data.user);
      const normalizedData = Object.assign({}, normalize(camelizedJson, userSchema));
      loginSuccess(normalizedData);

      const tokenPromise = setToken(data.key);
      const housePromise = getHouseList();
      Promise.all([tokenPromise, housePromise])
        .then(values => {
          setHouses(values[1]);
          getCitizenList(values[1].result[0])
            .then(citizens => {
              setCitizens(citizens);
              formikBag.setSubmitting(false);
              navigation.navigate("HomeScreen");
            })
            .catch(err => {
              formikBag.setSubmitting(false);
              alert(err);
            });
        })
        .catch(err => {
          console.log(err);
          formikBag.setSubmitting(false);
          alert(err);
        });
    })
    .catch(error => {
      alert(error);
      formikBag.setSubmitting(false);
    });
};

如您所见,我也在使用 normalizr 来解析数据。getHouseList()和函数的数据getCitizenList()在各自的函数中被标准化。

以下是测试:

const createTestProps = props => ({
  navigation: { navigate: jest.fn() },
  loginSuccess: jest.fn(),
  setHouses: jest.fn(),
  setCitizens: jest.fn(),
  ...props
});

...

describe("component methods", () => {
  let wrapper;
  let props;
  beforeEach(() => {
    props = createTestProps();
    wrapper = shallow(<LoginForm {...props} />);
    fetch.mockResponseOnce(JSON.stringify(userResponse));
    fetch.mockResponseOnce(JSON.stringify(housesResponse));
    fetch.mockResponseOnce(JSON.stringify(citizensResponse));
    wrapper
      .instance()
      .handleSubmit({ email: "abc", password: "def" }, { setSubmitting: jest.fn() });
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it("should dispatch a loginSuccess() action", () => {
    expect(props.loginSuccess).toHaveBeenCalledTimes(1);
  });
});

在此测试中,提供给jest-fetch-mocks(userResponse和) 的值是常数。我现在这个测试失败了,因为显然应该调度 Redux 操作的 which 永远不会被调用(即使我在函数中提供了 a )。housesResponsecitizensResponseloginSuccess()jest.fn()createProps()

我究竟做错了什么?为什么loginSuccess()从未调用该函数?

编辑:应布赖恩的要求,这里是 api 调用的代码:

export const apiLoginUser = (email, password) =>
  postRequestWithoutHeader(ROUTE_LOGIN, { email: email, password: password });

export const postRequestWithoutHeader = (fullUrlRoute, body) =>
  fetch(fullUrlRoute, {
    method: "POST",
    body: JSON.stringify(body),
    headers: { "Content-Type": "application/json" }
  }).then(response =>
    response.json().then(json => {
      if (!response.ok) {
        return Promise.reject(json);
      }
      return json;
    })
  );
4

1 回答 1

1

问题

断言props.loginSuccess()发生在调用它的代码运行之前。

细节

重要的是要记住 JavaScript 是单线程的并且工作在消息队列之外(请参阅并发模型和事件循环)。

它从队列中取出一条消息,运行相关函数直到堆栈为空,然后返回队列以获取下一条消息。

JavaScript 中的异步代码通过将消息添加到队列来工作。

then()在这种情况下,对inside的调用apiLoginUser()正在将消息添加到队列中,但在结束之间beforeEach()并且it('should dispatch a loginSucces() action')并非所有消息都有机会执行。

解决方案

解决方案是确保最终调用的排队消息loginSuccess()都有机会在执行断言之前运行。

有两种可能的方法:

方法一

handleSubmit()返回由创建的承诺apiLoginUser(),然后在结束时返回该承诺beforeEach()。返回PromisefrombeforeEach()将导致Jest 在运行测试之前等待它解决

方法二

等待Promise是理想的,但如果无法更改代码,则可以手动将测试中的断言延迟所需的事件循环周期数。最简洁的方法是使测试异步并等待已解决的Promise(或一系列承诺,如果需要多个事件循环周期):

it('should dispatch a loginSuccess() action', async () => {

  // queue the rest of the test so any pending messages in the queue can run first
  await Promise.resolve();

  expect(props.loginSuccess).toHaveBeenCalledTimes(1);

});
于 2018-08-28T19:42:51.373 回答