如何在redux操作(或操作中指定的其他回调)的上下文中使用jest测试`image.onload`

时间:2017-11-13 21:23:29

标签: javascript unit-testing react-redux jest

我的问题是我正在尝试对某个功能进行单元测试,但无法弄清楚如何测试它的一部分。

这是一个反应/减少行动,执行以下操作:

1)使用图片网址

检索json数据

2)将图像加载到Image实例中并将其大小调度到reducer(使用Image.onload加载图像时异步)

3)调度fetch已完成到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操作是否被广播?

1 个答案:

答案 0 :(得分:5)

经过一番调查,我发现了一个非常有趣的javascript函数,可以解决我的问题。

就是这样:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

以下是我使用Object.defineProperty(...)解决问题的方法:

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。 setter将继续像正常一样获取或设置,但也会保存设置为单元测试范围内的变量的值(在本例中为函数)。之后,您可以在测试的验证步骤之前运行您捕获的功能。

陷阱 - 可配置需要设置 - 请注意definePropertydefineProperties的功能不同 - 这在实际代码中是不好的做法。 - 记得使用原型

希望这篇文章可以帮助开发人员!