如何在Redux动作(或该动作中分配的其他回调)的上下文中使用Jest测试`image.onload`
我的问题是我试图对功能进行单元测试,但无法弄清楚如何测试部分功能.
My problem was that I am trying to make a unit test for a function but can't figure out how to test a part of it.
这是一个执行以下操作的react/redux动作:
This is a react / redux action that does the following:
1)使用图片网址检索json数据
1) retrieves json data with an image url
2)将图像加载到Image实例中,并将其大小分配给reducer(使用Image.onload加载图像时异步)
2) loads the image into an Image instance and dispatches its size to the reducer (asynchronously when image is loaded using Image.onload)
3)调度将获取操作完成给简化器
3) dispatches that the fetch was completed to the reducer
图像加载异步发生,因此当我尝试对其进行单元测试时,将不会调用它.而且,我不能随便嘲笑,因为图像实例是在函数中创建的.
The image onload happens asynchronously, so when I try to unit test it it wouldn't be called. Moreover, I can't just mock things out because the image instance is created within the function...
这是我要测试的代码(删除了一些检查,分支逻辑和内容):
Here's the code I wanted to test (removing some checks, branching logic, and stuff):
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.
Notice in that last storeImageSize
private function how there's an instance of Image created and an image.onload that is assigned to a function.
现在这是我的测验:
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
回调之前完成.
This test though will fail because the test finishes before the image.onload
callback is called.
如何强制调用image.onload
回调,以便测试是否可以广播`STORE_CARD_IMAGE_SIZE动作?
How can I force the image.onload
callback to be called so that I can test that the `STORE_CARD_IMAGE_SIZE action gets broadcasted?
经过一番调查,我发现了一个非常有趣的javascript函数,可以解决我的问题.
After some investigation, I found a very interesting javascript function that would solve my issue.
这是我使用Object.defineProperty(...)
解决问题的方式:
Here's how I used Object.defineProperty(...)
to solve my issue:
describe('fetchInsuranceCardPhoto', () => {
let imageOnload = null;
/** Override Image global to save onload 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.设置器将继续正常获取或设置,但还将保存在单元测试范围内设置为变量的值(在这种情况下为函数).之后,您可以运行在测试的验证步骤之前捕获的功能.
What I did here was use define property to override the setter of any instance of Image
. the setter would continue to get or set like normal but would also save the value (in this case a function) that was set to a variable in the scope of the unit test. After which, you can just run that function you captured before the verification step of your the test.
陷阱
-需要设置可配置
-请注意,defineProperty
是与defineProperties
不同的功能
-这在实际代码中是不好的做法.
-记得使用原型
Gotchas
- configurable needs to be set
- note that defineProperty
is a different function than defineProperties
- This is bad practice in real code.
- remember to use the prototype
希望这篇文章可以帮助需要帮助的开发人员!
Hope this post can help a dev in need!