我正在为我们的一个项目创建一个新的React组件,而且我几乎坚持为它编写一个合适的测试。我已经阅读了不少文档和博客文章,但我似乎无法让它运行。
对我而言,承诺似乎没有被执行。当我使用调试器运行测试时,它不会在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()函数都会被执行。
我已经在这个问题上花了太多时间,而且我对如何继续下去毫无头绪。我做错了什么?
正如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.
答案 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中。因此,如果某人有解释,那将非常感激。