承诺没有在Jest测试中执行

时间:2018-05-06 14:01:16

标签: javascript reactjs testing jestjs es6-promise

我正在为我们的一个项目创建一个新的React组件,而且我几乎坚持为它编写一个合适的测试。我已经阅读了不少文档和博客文章,但我似乎无法让它运行。

TL; DR

对我而言,承诺似乎没有被执行。当我使用调试器运行测试时,它不会在Promise的函数中停止,也不会在then()函数中停止。但是,它将在测试本身的then / catch函数中停止。

守则

因此,该组件实际上非常简单。目前它应该通过API搜索位置。它的测试看起来像这样:

import axios from 'axios';
import React from 'react';
import {shallowWithIntl} from "../../../Helpers/react-intl-helper";
import Foo from "../../../../src/Components/Foo/Foo";
import {mount} from "enzyme";

const queryTerm = 'exampleQueryTerm';
const locationAgs = 'exampleLocationKey';

const fakeLocationObject = {
  search: '?for=' + queryTerm + '&in=' + locationAgs
};

jest.mock('axios', () => {
  const exampleLocations = [{
    data: {"id": "expected-location-id"}
  }];

  return {
    get: jest.fn().mockReturnValue(() => {
      return Promise.resolve(exampleLocations)
    })
  };
});

let fooWrapper, instance;

beforeEach(() => {
  global.settings = {
    "some-setting-key": "some-setting-value"
  };

  global.URLSearchParams = jest.fn().mockImplementation(() => {
    return {
      get: function(param) {
        if (param === 'for') return queryTerm;
        else if (param === 'in') return locationAgs;
        return '';
      }
    }
  });

  fooWrapper = shallowWithIntl(<Foo location={fakeLocationObject} settings={ global.settings } />).dive();
  instance = fooWrapper.instance();
});

it('loads location and starts result search', function() {
  expect.assertions(1);

  return instance
    .searchLocation()
    .then((data) => {
      expect(axios.get).toHaveBeenCalled();
      expect(fooWrapper.state('location')).not.toBeNull();
    })
    .catch((error) => {
      expect(fooWrapper.state('location')).toBe(error);
    });
});

所以,你可以看到测试应该在Foo组件实例上调用searchLocation,它返回一个Promise对象,你可以(几乎)在它的实现中看到它。

import React, { Component } from 'react';
import { injectIntl } from "react-intl";
import {searchLocationByKey} from "../../Services/Vsm";

class Foo extends Component {

  constructor(props) {
    super(props);

    this.state = {
      location: null,
      searchingLocation: false,
      searchParams: new URLSearchParams(this.props.location.search)
    };
  }

  componentDidUpdate(prevProps) {
    if (!prevProps.settings && this.props.settings) {
      this.searchLocation();
    }
  }

  searchLocation() {
    this.setState({
      searchingLocation: true
    });

    const key = this.state.searchParams.get('in');

    return searchLocationByKey(key)
      .then(locations => {
        this.setState({ location: locations[0], searchingLocation: false })
      })
      .catch(error => console.error(error));
  }

  render() {
    // Renders something
  };

}

export default injectIntl(Foo);

输入searchLocationByKey

function requestLocation(url, resolve, reject) {
  axios.get(url).then(response => {
    let locations = response.data.map(
      location => ({
        id: location.collectionKey || location.value,
        rs: location.rs,
        label: location.label,
        searchable: location.isSearchable,
        rawData: location
      })
    );

    resolve(locations);
  }).catch(error => reject(error));
}

export const searchLocationByKey = function(key) {
  return new Promise((resolve, reject) => {
    let url = someGlobalBaseUrl + '?regional_key=' + encodeURIComponent(key);
    requestLocation(url, resolve, reject);
  });
};

问题

这是测试的输出:

Error: expect(received).toBe(expected)

Expected value to be (using ===):
  [Error: expect(received).not.toBeNull()

Expected value not to be null, instead received
  null]
Received:
  null

我必须承认,我对Promises,React和JavaScript测试都很陌生,所以我可能会混淆几件事。正如我上面所写,似乎Promise没有正确执行。调试时,它不会在Foo.searchLocation中定义的then()函数中停止。相反,显然,测试中定义的then()和catch()函数都会被执行。

我已经在这个问题上花了太多时间,而且我对如何继续下去毫无头绪。我做错了什么?

更新1:done()函数

正如El Aoutar Hamza在下面的答案中指出的那样,可以将一个函数(通常称为“done”)传递给测试函数。我完成了这个:

it('loads location and starts result search', function(done) {
  expect.assertions(1);

  return instance
    .searchLocation()
    .then((data) => {
      expect(fooWrapper.state('location')).not.toBeNull();
      done();
    })
    .catch((error) => {
      expect(fooWrapper.state('location')).toBe(error);
    });
});

但我最终得到了这个错误:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

2 个答案:

答案 0 :(得分:0)

requestLocation内置您正在尝试访问response.data,在模拟axios.get时,您将返回使用数组解析的Promise!您应该返回一个使用data属性(包含数组)的对象解析的Promise。

jest.mock('axios', () => ({
  get: jest.fn(() => Promise.resolve({
    data: [{ "id": "expected-location-id" }]
   }))
}));

另一点是,在测试异步代码时,测试将在调用回调之前完成,这就是为什么你应该考虑为你的测试提供一个名为done的参数,这样,jest将等到完成回调是调用。

describe('Foo', () => {
    it('loads location and starts result search', done => {
      expect.assertions(1);

      return instance
        .searchLocation()
        .then((data) => {
          expect(fooWrapper.state('location')).not.toBeNull();
          done();
        })
        .catch((error) => {
          expect(fooWrapper.state('location')).toBe(error);
          done();
        });
    });
});

答案 1 :(得分:0)

就像我在El Aoutar Hamza回答的最新评论中提到的那样,我找到了一个解决方案,感谢能够帮助我的同事。

似乎无法将承诺从Foo.searchLocation返回到测试。我们需要做的是将代码从searchLocationByKey包装起来并处理到另一个Promise,看起来像这样:

import React, { Component } from 'react';
import { injectIntl } from "react-intl";
import {searchLocationByKey} from "../../Services/Vsm";

class Foo extends Component {

  constructor(props) {
    super(props);

    this.state = {
      location: null,
      searchingLocation: false,
      searchParams: new URLSearchParams(this.props.location.search)
    };
  }

  componentDidUpdate(prevProps) {
    if (!prevProps.settings && this.props.settings) {
      this.searchLocation();
    }
  }

  searchLocation() {
    this.setState({
      searchingLocation: true
    });

    const key = this.state.searchParams.get('in');

    return new Promise((resolve, reject) => {
      searchLocationByKey(key)
        .then(locations => {
          this.setState({ location: locations[0], searchingLocation: false });
          resolve();
        })
        .catch(error => {
          console.error(error));
          reject();
        }
    });
  }

  render() {
    // Renders something
  };

}

export default injectIntl(Foo);

只有这样,Jest才能正确地接受承诺,并且一切正常,因为我预计它将首先出现。

我仍然不明白为什么承诺不能简单地返回,而是需要包装在另一个Promise中。因此,如果某人有解释,那将非常感激。