Обещание не выполняется в тесте Jest - PullRequest
0 голосов
/ 06 мая 2018

В настоящее время я создаю новый компонент 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);
    });
});

Итак, как вы можете видеть, тест должен вызывать searchLocation для экземпляра компонента Foo, который возвращает объект 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, поэтому я мог бы перепутать несколько вещей. Как я писал выше, кажется, что Обещание не выполняется должным образом. При отладке он не остановится в функции then (), определенной в Foo.searchLocation. Вместо этого, очевидно, выполняются обе функции then () и catch (), определенные в тесте.

Я уже потратил слишком много времени на эту проблему, и я не знаю, что делать дальше. Что я делаю не так?

Обновление 1: функция 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 голосов
/ 08 мая 2018

Итак, как я уже упоминал в своем последнем комментарии под ответом Эль Аутара Хамзы, я нашел решение благодаря коллеге, который смог мне помочь.

Кажется, что невозможно вернуть Обещание с Foo.searchLocation на тест. Нам нужно было заключить код, получающий и обрабатывающий Обещание из searchLocationByKey, в еще одно Обещание, которое выглядит следующим образом:

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

Только тогда Джест смог правильно выполнить обещание, и все сработало так, как я и ожидал.

Я все еще не понимал, почему обещание не может быть просто возвращено и должно быть обернуто в другое обещание. Так что, если у кого-то есть объяснение этому, оно будет с благодарностью.

0 голосов
/ 06 мая 2018

Внутри requestLocation вы пытаетесь получить доступ к response.data, а при издевательстве axios.get вы возвращаете Обещание, разрешенное с массивом ! вместо этого вы должны вернуть Обещание, разрешенное с помощью объекта со свойством data (которое содержит массив).

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