Search code examples
javascriptunit-testingqunitaframe

How to unit test an A-Frame component with Qunit?


Let's imagine we have a basic A-Frame component:

AFRAME.registerComponent('scale-on-mouseenter', {
    schema: {
        to: {
            default: '2 2 2'
        }
    },
    init: function () {
        this.el.addEventListener('mouseneter', function () {
            this.setAttribute('scale', data.to);
        });
    }
});

And I want to test through QUnit. How to test if this component creates the scale attribute?

Should I create a "testing A-Scene" for this purpose and verify the DOM? Or is there a more "unit" way to test?


Solution

  • The problem presented here is divided in two parts.

    • Testing a component
    • Testing an EventListener

    Testing a component

    The link provided by ngokevin gives a solution. More especially, looking at existing tests shows that we need to create a testing a-scene https://github.com/aframevr/aframe/blob/master/tests/components/scale.test.js

    That's not really a unitary test, but hey, I do not want to mock all the A-Frame library!

    Let's start with a more basic code to test, without an EventListener.

    // Set a scale factor to 2 2 2
    AFrame.registerComponent('big', {
        init: function () {
            this.el.setAttribute('scale', '2 2 2');
        }
    });
    

    The associated test needs to create a testing a-scene. We can use QUnit.module for that.

    QUnit.module('Component testing', {
        before: function () {
            var scene = document.createElement('a-scene');
            document.querySelector('#qunit-fixture').appendChild(scene);
        },
        after: function () {
            var scene = document.querySelector('#qunit-fixture > a-scene'); 
            scene.parentNode.removeChild(scene);
        }
    });
    

    And now, we can test the component by creating an a-entity, and see if the attribute is created when the component is added to the tag. We just have to wait for the component to be loaded. Otherwise, the assert is made before the component is loaded, and will eventually fail.

    QUnit.test('Big add scale to 2 2 2', function (assert) {
        // Create the entity to test
        var entity = document.createElement('a-entity');
        entity.setAttribute('big', '');
    
        // Add it to the testing a-scene
        var scene = document.querySelector('#qunit-fixture > a-scene');
        scene.appendChild(entity);
    
        // Wait for the component to be loaded
        var done = assert.async()
        entity.addEventListener('loaded', function () {
            // Actual test
            assert.deepEqual(
                entity.getAttribute('scale'), 
                {'x': 2, 'y': 2, 'z': 2});
            done();
        });
    });
    

    Testing an EventListener

    The original problem involved an EventListener. As a reminding purpose, this was the code to test.

    AFRAME.registerComponent('scale-on-mouseenter', {
        schema: {
            to: {
                default: '2 2 2'
            }
        },
        init: function () {
            this.el.addEventListener('mouseneter', function () {
                this.setAttribute('scale', data.to);
            });
        }
    });
    

    Testing this needs another trick. One solution is to create a named function, then add this function as handler in the EventListener as described here. The tests will test the named function alone, but not the addEventListener part.

    A second solution is to use the setTimeout trick as described here . The final test will use the previous work to test the component, then dispatch an Event, then use the assert part inside a setTimeout to queue the test. A timeout of 0 works very well.

    QUnit.test('scale-on-mouseenter add eventlistener', function (assert) {
        // Create the entity to test
        var entity = document.createElement('a-entity');
        entity.setAttribute('scale-on-mouseenter', '');
    
        // Add it to the testing a-scene
        var scene = document.querySelector('#qunit-fixture > a-scene');
        scene.appendChild(entity);
    
        // Wait for the component to be loaded
        var done = assert.async()
        entity.addEventListener('loaded', function () {
            // Dispatch the event
            entity.dispatchEvent(new Event("mouseenter"));
            // Queue the test with a timeout of 0
            setTimeout(function () {
                // Actual test
                assert.deepEqual(
                    entity.getAttribute('scale'), 
                    {'x': 2, 'y': 2, 'z': 2});
                done();
            });
        });
    });