简体   繁体   English

使用酶浅渲染与Sinon进行存根

[英]Stubbing with Sinon using Enzyme shallow rendering

I have a React component that I've migrated over from a JS Component. 我有一个从JS组件迁移过来的React组件。 I'm migrating over and checking the tests and I've got lots of failures because stubbing doesn't seem to be working anymore. 我正在迁移并检查测试,但我遇到了很多失败,因为存根似乎不再起作用。 Here's my component... 这是我的组成部分...

import DeleteButton from "./delete-button.jsx"
import Dialogs from "../../dialogs";
import React from "react";
import UrlHelper from "../../helpers/url-helper";

export default class ActiveDeleteButton extends React.Component {

    /**
     * Creates an instance of ActiveDeleteButton.
     * 
     * @param {object} props The react props collection.
     * 
     * @memberOf ActiveDeleteButton
     */
    constructor(props) {
        super (props);

        this.handleConfirmDelete = this.handleConfirmDelete.bind(this);
    }

    handleConfirmDelete() {
        $.ajax({
            url: this.props.deleteUri,
            type: `DELETE`,
            contentType: `application/json; charset=utf-8`,
            cache: false,
            success: (xhr) => {
                let successUri = this.props.successUri;
                if (!successUri && xhr && xhr.uri) { successUri = xhr.uri; }
                if (successUri) { UrlHelper.redirect(successUri); }
            },
            error: (xhr, status) => {
                this.showFailed();
            }
        });
    }

    /**
     * Shows failure of deletion.
     * 
     * @memberOf ActiveDeleteButton
     */
    showFailed() {
        Dialogs.alert(this.props.errorMessage);
    }

    /**
     * Renders the component to the DOM.
     * 
     * @returns the HTML to render.
     * 
     * @memberOf ActiveDeleteButton
     */
    render() {
        return (
            <DeleteButton text = {this.props.text}
                          title = {this.props.title}
                          cancelText = {this.props.cancelText}
                          confirmText = {this.props.confirmText}
                          message = {this.props.message}
                          onConfirmDelete = {this.handleConfirmDelete} />
        );
    }
}

And here's the test (condensed)... 这是测试(精简)...

describe("performs a DELETE AJAX request", () => {

    it ("for specified URLs", sinon.test(function() {
        let wrapper = shallow(<ActiveDeleteButton text = "Click Me"  />);
        let instance = wrapper.instance(); 
        let ajaxStub = this.stub($, 'ajax');
        instance.forceUpdate()
        wrapper.update()
        instance.handleConfirmDelete();
        console.log(ajaxStub.getCall(0));
        let options = ajaxStub.getCall(0).args[0];
        assert.equal(options.url, objUt.deleteUri);
        assert.equal(options.type, "DELETE");
    }));
}));

The issue I have is that 'ajaxStub.getCall(0)' returns null. 我的问题是'ajaxStub.getCall(0)'返回null。 This should return the Ajax call so I can check the args (and it used to before in my old JS component). 这应该返回Ajax调用,以便我可以检查args(它在我的旧JS组件中以前使用过)。 The stub is never called although it (to my mind) clearly should be. 尽管(在我看来)该存根显然应该被调用,但它从未被调用。

Am I missing something here? 我在这里想念什么吗?

This is more a workaround than anything else so a better answer would be great. 这比其他任何方法都更可行,因此更好的答案将是不错的选择。 In the end I built a workaround for this as follows... 最后,我为此建立了一个解决方法,如下所示...

Firstly I created a new class to handle Ajax Requests. 首先,我创建了一个新类来处理Ajax请求。

/**
 * An AJAX request wrapper.
 * Usage of this enables testing AJAX calls.
 * 
 * @export AjaxRequest
 * @class AjaxRequest
 * @extends {AjaxRequest}
 */
export default class AjaxRequest {

    /**
     * Creates an instance of AjaxRequest.
     * @param {any} { url, type, contentType, cache, data, successCallback, errorCallback } 
     * 
     * @memberOf AjaxRequest
     */
    constructor({ url, type, contentType, cache, data, successCallback, errorCallback }) {
        Guard.throwIf(url, "url");
        let emptyFunc = () => {};

        this.url = url;
        this.type = type.toUpperCase() || "GET";
        this.contentType = contentType || "application/json; charset=utf-8";
        this.dataType = "json";
        this.cache = cache || false;
        this.data = data ? JSON.stringify(data) : undefined;
        this.successCallback = successCallback || emptyFunc;
        this.errorCallback = errorCallback || emptyFunc;
    }

    /**
     * Executes the AJAX request.
     * 
     * 
     * @memberOf AjaxRequest
     */
    execute() {
        $.ajax({
            url: this.url,
            type: this.type,
            contentType: this.contentType,
            dataType: this.dataType,
            cache: this.cache,
            data: this.data,
            success: this.successCallback,
            error: this.errorCallback
        });
    }

    /**
     * Gets a GET request.
     * 
     * @static
     * @param {string} url 
     * @param {function} successCallback 
     * @param {function} errorCallback 
     * @returns an AjaxRequest
     * 
     * @memberOf AjaxRequest
     */
    static get(url, successCallback, errorCallback) {
        return new AjaxRequest({
            url: url,
            type: 'GET',
            successCallback: successCallback,
            errorCallback: errorCallback
        });
    }

    /**
     * Gets a POST request.
     * 
     * @static
     * @param {string} url 
     * @param {object} data
     * @param {function} successCallback 
     * @param {function} errorCallback 
     * @returns an AjaxRequest
     * 
     * @memberOf AjaxRequest
     */
    static post(url, data, successCallback, errorCallback) {
        return new AjaxRequest({
            url: url,
            data: data,
            type: 'POST',
            successCallback: successCallback,
            errorCallback: errorCallback
        });
    }

    /**
     * Gets a PUT request.
     * 
     * @static
     * @param {string} url 
     * @param {object} data
     * @param {function} successCallback 
     * @param {function} errorCallback 
     * @returns an AjaxRequest
     * 
     * @memberOf AjaxRequest
     */
    static put(url, data, successCallback, errorCallback) {
        return new AjaxRequest({
            url: url,
            data: data,
            type: 'PUT',
            successCallback: successCallback,
            errorCallback: errorCallback
        });
    }

    /**
     * Gets a DELETE request.
     * 
     * @static
     * @param {string} url
     * @param {function} successCallback 
     * @param {function} errorCallback 
     * @returns an AjaxRequest
     * 
     * @memberOf AjaxRequest
     */
    static delete(url, successCallback, errorCallback) {
        return new AjaxRequest({
            url: url,
            type: 'DELETE',
            successCallback: successCallback,
            errorCallback: errorCallback
        });
    }
}

(i feel there's a certain amount of wheel re-inventing here). (我觉得这里有一定数量的车轮重新发明)。 I then updated my method call in my component as follows... 然后,我在组件中更新了我的方法调用,如下所示...

handleConfirmDelete() {
    AjaxRequest.delete(this.props.deleteUri,
        (xhr) => {
            let successUri = this.props.successUri;
            if (!successUri && xhr && xhr.uri) { successUri = xhr.uri; }
            if (successUri) { UrlHelper.redirect(successUri); }
        },
        (xhr, status) => {
            this.showFailed();
        }
    ).execute();
}

I can now test as follows... 我现在可以测试如下...

describe("performs a DELETE AJAX request", () => {
    let wrapper = null;
    let instance = null;
    let ajaxStub = null;
    let urlHelperRedirectStub = null;

    beforeEach(() => {
        ajaxStub = sinon.stub(AjaxRequest.prototype, 'execute');
        urlHelperRedirectStub = sinon.stub(UrlHelper, 'redirect');
        wrapper = shallow(<ActiveDeleteButton text = "Click Me"  />);
        instance = wrapper.instance(); 
    });

    afterEach(() => {
        ajaxStub.restore();
        urlHelperRedirectStub.restore();
    });

    it ("for default URLs", sinon.test(function() {
        instance.handleConfirmDelete();
        sinon.assert.called(ajaxStub);
        let requestInfo = ajaxStub.getCall(0).thisValue;          

        assert.equal(UrlHelper.current.url().split('?')[0], requestInfo.url);
    }));

    it ("for specified URLs", sinon.test(function() {
        wrapper = shallow(<ActiveDeleteButton text = "Click Me" deleteUri="http://localhost/items/12" />);
        instance = wrapper.instance();

        instance.handleConfirmDelete();
        sinon.assert.called(ajaxStub);
        let requestInfo = ajaxStub.getCall(0).thisValue;          

        assert.equal("http://localhost/items/12", requestInfo.url);
    }));
}));

Using the thisValue property of the stub call I can get the callbacks and execute them manually to test them depending on different input. 使用存根调用的thisValue属性,我可以获取回调并手动执行它们以根据不同的输入对其进行测试。 Not an ideal solution given the effort needed but it works and its reusable. 考虑到需要的努力,这不是理想的解决方案,但它可行且可重用。

I feel there must be a better way however. 我觉得必须有更好的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM