最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Promise not being executed in Jest test - Stack Overflow

programmeradmin0浏览0评论

I'm currently creating a new React ponent for one of our projects and I'm pretty much stuck with writing a proper test for it. I've read quite a few docs and blog posts and more but I can't seem to get it running.

TL;DR

To me, it seems the Promise is not executed. When I run the test with a debugger, it won't stop in the Promise's function and neither in the then() function. It will, however, stop in the then/catch functions in the test itself.

The Code

So, the ponent is actually fairly simple. For the time being it is supposed to search for a location via an API. The test for it looks like this:

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);
    });
});

I'm currently creating a new React ponent for one of our projects and I'm pretty much stuck with writing a proper test for it. I've read quite a few docs and blog posts and more but I can't seem to get it running.

TL;DR

To me, it seems the Promise is not executed. When I run the test with a debugger, it won't stop in the Promise's function and neither in the then() function. It will, however, stop in the then/catch functions in the test itself.

The Code

So, the ponent is actually fairly simple. For the time being it is supposed to search for a location via an API. The test for it looks like this:

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);
    });
});

So, as you can see the test is supposed to call searchLocation on the Foo ponent instance, which returns a Promise object, as you can (almost) see in its implementation.

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)
    };
  }

  ponentDidUpdate(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);

Enter 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);
  });
};

The Problem

This is the output of the test:

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

I have to admit that I'm pretty new to Promises, React and JavaScript testing, so I might have mixed up several things. As I wrote above, it seems that the Promise is not executed properly. When debugging, it will not stop in the then() function defined in Foo.searchLocation. Instead, apparently, both the then() and catch() functions defined in the test are executed.

I've spent way too much time on this issue already and I'm clueless on how to go on. What am I doing wrong?

Update 1: done() function

As El Aoutar Hamza pointed out in an answer below, it is possible to pass a function (usually called "done") to the test function. I've done exactly this:

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);
    });
});

But I end up getting this error:

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Share Improve this question edited Nov 20, 2018 at 6:26 skyboyer 23.8k7 gold badges62 silver badges71 bronze badges asked May 6, 2018 at 14:01 tBurecktBureck 2751 gold badge3 silver badges10 bronze badges 0
Add a ment  | 

2 Answers 2

Reset to default 3

Inside requestLocation you are trying to access response.data, and when mocking axios.get, you are returning a Promise resolved with an array ! you should instead return a Promise resolved with an object with data property (that contains the array).

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

Another point is that when testing asynchronous code, the test will finish before even calling the callbacks, that's why you should consider providing your test an argument called done, that way, jest will wait until the done callback is called.

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();
        });
    });
});

So like I mentioned in my latest ment under El Aoutar Hamza's answer, I have found a solution thanks to a colleague who was able to help me.

It seems that it is not possible to return the Promise from Foo.searchLocation on to the test. What we needed to do was to wrap the code getting and handling the Promise from searchLocationByKey into yet another Promise, which looks like this:

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)
    };
  }

  ponentDidUpdate(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);

Only then was Jest able to properly hook into the promise and everything worked as I expected it to be in the first place.

I still didn't understand why the promise cannot simply be returned and needs to be wrapped in another Promise, though. So if someone has an explanation for that it would be greatly appreciated.

发布评论

评论列表(0)

  1. 暂无评论