我的问题是我正在尝试对函数进行单元测试,但无法弄清楚如何测试它的一部分.
这是一个react / redux动作,它执行以下操作:
1)使用图像URL检索json数据
2)将图像加载到Image实例并将其大小调度到reducer(使用Image.onload加载图像时异步)
3)将完成提取的调度发送给reducer
图像onload是异步发生的,所以当我尝试对它进行单元测试时,就不会调用它.而且,我不能只是嘲笑事物,因为图像实例是在函数内创建的……
这是我想要测试的代码(删除一些检查,分支逻辑和东西):
export function fetchInsuranceCardPhoto() {
return dispatch => {
dispatch(requestingInsuranceCardPhoto());
return fetch(`${api}`,
{
headers: {},
credentials: 'same-origin',
method: 'GET',
})
.then(response => {
switch (response.status) {
case 200:
return response.json()
.then(json => {
dispatch(receivedInsuranceCardPhoto(json));
})
}
});
};
}
function receivedInsuranceCardPhoto(json) {
return dispatch => {
const insuranceCardFrontImg = json.insuranceCardData.url_front;
const insuranceCardBackImg = json.insuranceCardData.url_back;
if (insuranceCardFrontImg) {
dispatch(storeImageSize(insuranceCardFrontImg, 'insuranceCardFront'));
}
return dispatch(receivedInsuranceCardPhotoSuccess(json));
};
}
function receivedInsuranceCardPhotoSuccess(json) {
const insuranceCardFrontImg = json.insuranceCardData.url_front;
const insuranceCardBackImg = json.insuranceCardData.url_back;
const insuranceCardId = json.insuranceCardData.id;
return {
type: RECEIVED_INSURANCE_CARD_PHOTO,
insuranceCardFrontImg,
insuranceCardBackImg,
insuranceCardId,
};
}
function storeImageSize(imgSrc, side) {
return dispatch => {
const img = new Image();
img.src = imgSrc;
img.onload = () => {
return dispatch({
type: STORE_CARD_IMAGE_SIZE,
side,
width: img.naturalWidth,
height: img.naturalHeight,
});
};
};
}
请注意,最后一个storeImageSize私有函数是如何创建Image实例的,以及分配给函数的image.onload.
现在这是我的测试:
it('triggers RECEIVED_INSURANCE_CARD_PHOTO when 200 returned without data', async () => {
givenAPICallSucceedsWithData();
await store.dispatch(fetchInsuranceCardPhoto());
expectActionsToHaveBeenTriggered(
REQUESTING_INSURANCE_CARD_PHOTO,
RECEIVED_INSURANCE_CARD_PHOTO,
STORE_CARD_IMAGE_SIZE,
);
});
此测试虽然会失败,因为测试在调用image.onload回调之前完成.
如何强制调用image.onload回调以便我可以测试`STORE_CARD_IMAGE_SIZE操作是否被广播?
解决方法:
经过一番调查,我找到了一个非常有趣的javascript函数来解决我的问题.
这是我使用Object.defineProperty(…)来解决我的问题的方法:
describe('fetchInsuranceCardPhoto', () => {
let imageOnload = null;
/** Override Image global to save onl oad setting here so that I can trigger it manually in my test */
function trackImageOnload() {
Object.defineProperty(Image.prototype, 'onload', {
get: function () {
return this._onload;
},
set: function (fn) {
imageOnload = fn;
this._onload = fn;
},
});
}
it('triggers RECEIVED_INSURANCE_CARD_PHOTO when 200 returned with data', async () => {
trackImageOnload();
givenAPICallSucceedsWithData();
await store.dispatch(fetchInsuranceCardPhoto());
imageOnload();
expectActionsToHaveBeenTriggered(
REQUESTING_INSURANCE_CARD_PHOTO,
RECEIVED_INSURANCE_CARD_PHOTO,
STORE_CARD_IMAGE_SIZE,
);
});
我在这里做的是使用define属性覆盖任何Image实例的setter. setter将继续像正常一样获取或设置,但也会保存设置为单元测试范围内的变量的值(在本例中为函数).之后,您可以在测试的验证步骤之前运行您捕获的功能.
陷阱
– 可配置需要设置
– 请注意,defineProperty是一个与defineProperties不同的函数
– 这在实际代码中是不好的做法.
– 记得使用原型
希望这篇文章可以帮助一个有需要的开发者!