3
\$\begingroup\$

I'm having some trouble isolating the parts of my code enough for unit testing to make them simple and easy to maintain. The code I'm writing feels like an integration test because most of what I'm doing in the test.

I'm curious on how I can both make this code better and more unit-testy. This is just a single unit test out of multiple I have for the given class. It runs independently from all others.:

//Utilities & Short names
var new_CR = (sceneCls, opts) => {return new global.cr.CompactRenderer(sceneCls, opts);};
var new_$ = (tagName) => {return $(document.createElement(tagName));};

var DummyS = global.cr.DummyScene;


QUnit.test("jEl, no takeOwnership - new(), place(), $()", function(assert){
    //No jEl passed anywhere is an error
    assert.throws(new_CR(DummyS).place, "throws when no arguments");

    //jEl can be provided in options or in the place function itself
    //Should work the same way in the end
    var codePaths = [
        (jDiv) => {
            var cr = new_CR(DummyS, {jEl : jDiv});
            return cr;
        },
        (jDiv) => {
            var cr = new_CR(DummyS);
            cr.place(jDiv);
            return cr;
        }
    ];
    codePaths.forEach((codePath) => {
        var jDiv = new_$("div");
        var cr = codePath(jDiv);

        assert.ok(jDiv.children().length === 1,
            "provided div (placement el, jEl) should contain only 1 child");
        assert.notOk(jDiv.hasClass("CompactRenderer"),
            "provided div (placement el, jEl) should NOT have identifying custom class");
        assert.ok(jDiv.children().first().hasClass("CompactRenderer"),
            "actual child el of placement el (jEl) should have identifying custom class");
        assert.notEqual(jDiv[0], cr.$()[0], "container should not equal $()");
        assert.equal(jDiv.children().first()[0], cr.$()[0], "child should equal $()");

        assert.throws(cr.place, "placing after already placed should error");
    });
});

Here's the relevant parts of the code that the unit test runs on for context. I'm not concerned with a review of this part of the code except in how it relates to the above unit test:

/**A component that handles a THREEjs renderer and all misc functions of it including
 * resizing, play/pause, fps stats, debugging, and memory management. Renders a BaseScene.
 * This will automatically call .place() if given a jEl.
 *
 * @constructor
 *
 * @param {BaseScene} sceneClass The scene to render, if specified in options, will be overwritten
 * @param {object} options Options object that gets merged with object on
 * initialization. Can contain any of the properties accessible on this object described
 * in this doc.
 * 
 * @prop {(string|number)} resize.width See applyResizeMethod
 * @prop {(string|number)} resize.height See applyResizeMethod
 * @prop {number} resize.aspect See applyResizeMethod
 * @prop {boolean} resize.fullscreen See applyResizeMethod
 * @prop {BaseScene} sceneClass Class to create as the scene
 * @prop {boolean} paused Start the renderer paused
 * @prop {boolean} takeOwnership If true, will take ownership of the given jquery element
 * such that it will change classes of that element and other stuff. Otherwise
 * it will only use the given element as a container for itself.
 * 
 */
export function CompactRenderer(sceneClass, options){
    var defaults = {
        resize : {
            width : null,
            height : null,
            aspect : null,
            fullScreen : null
        },

        sceneClass : sceneClass,
        paused : false,

        jEl : null, //jQuery element for where we should put the renderer
        takeOwnership : false   //If true, will use jEl as our container instead of just
                                //putting our own container inside of jEl
    };

    //Internal properties (shouldn't be controllable from external options)
    var internalDefaults = {
        resizeDynamicFunc : null, //Called to update element h and w if resize happens
        resizeTimeout : null,
        webgl_renderer : null,
        webgl_renderRAFId : null, //ID from requestAnimationFrame
        webgl_renderFunc : null,
        scene : null,

        webgl_render()
        {
            this._webgl_preRender();
            this._webgl_render();
        },
        webgl_renderStats()
        {
            this._webgl_preRender();
            this.stats.begin();
            this._webgl_render();
            this.stats.end();
        },
        _webgl_preRender()
        {
            this.webgl_renderRAFId = requestAnimationFrame(this.webgl_renderFunc);
        },
        _webgl_render()
        {
            this.scene.render(this.webgl_renderer);
        },

        stats : null
    };

    Object.assign(this, defaults, options, internalDefaults);

    //Set everything that doesn't rely on the DOM
    this.webgl_renderFunc = this.webgl_render.bind(this);

    // create the scene class
    var SceneClass = this.sceneClass;
    this.scene = new SceneClass();

    //If we have an element already, then place the renderer in the DOM and do stuff
    if(util.isDef(this.jEl))
    {
        this._place();
    }
}

/**Place the CompactRenderer into the given place in the DOM. The constructor
 * will automatically call this is the jQuery element is given at construction
 * time. Don't call more than once
 * @function
 * @param {jquery} jEl Jquery element of where to place the renderer
 */
CompactRenderer.prototype.place = function(jEl){
    this.jEl = jEl;
    return this._place();
};
/**Return the jQuery element for this renderer
 * @function
 * @returns {jquery}
 */
CompactRenderer.prototype.$ = function(){
    return this.jEl;
};
CompactRenderer.prototype._place = function(){
    if(util.isDef(this.THREE()))
    {
        throw new Error("Renderer has already been placed");
    }

    //If not take ownership of parent, make our own container and just add it
    if(!this.takeOwnership)
    {
        var newJEl = $(document.createElement("div"));
        this.$().append(newJEl);
        this.jEl = newJEl;
    }
    this.jEl.addClass("CompactRenderer");

    //Make a renderer
    //TODO: Different rendering levels
    //var rendererOptions = { antialias : true };

    this.webgl_renderer = new THREE.WebGLRenderer();

    this.webgl_renderer.shadowMap.enabled = true;
    //this.webgl_renderer.shadowMap.type = THREE.PCFSoftShadowMap;

    //Do all css and sizing
    this.$().append( this.webgl_renderer.domElement );
    this.applyResizeMethod(this.get("resize"));
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.