Search code examples
typescriptunit-testingmockingmocha.jslit-element

How to properly stub an element when unit testing a LitElement component?


I'm trying to unit test LitElement components. When trying to isolate components, I got perplexed how to actually stub elements. That is, I cannot find a solution how to replace a proper element with a hollowed one.
The polymer project on the topic of unit testing mentions this I failed to find the definition of the replace() function to see the implementation details. Actually, what is described by the polymer project in the "Create Stub Elements" heading is what I'm looking for.

Element definition file

export class AppElement extends LitElement {
    render() {
        return html`
            <header-element class="header"></header-element>
            <div class="body">
                <menu-element></menu-element>
                <social-media-element></social-media-element>
                <contacts-element></contacts-element>
                <tap-list-element name="Fridge List" display="fridge"></tap-list-element>
                <tap-list-element route="tap" name="Tap List" display="tap" ></tap-list-element>
                <home-element></home-element>
                <about-us-element></about-us-element>
                <not-found-element></not-found-element>
            </div>
       `;
   }
}

Element testing file

describe("Test Case for the AppElement Component", function() {
beforeEach(() => {
    app = new AppElement();
    document.body.appendChild(app);
    shadow = app.shadowRoot;
    app.updateComplete.then(() => {
        const tapListStub = new TapListElementStub();
        const tapListNodes = shadow.querySelectorAll('tap-list-element');
        console.log(app);
        const tapListLength = tapListNodes.length;
        const tapListNode = tapListLength === 1 ? tapListNodes[0] : null;
        tapListNode.replaceWith(tapListStub);
    });
});

it("should have the node for tap-list replaced with a stub", function(done) {
    app.updateComplete.then(() => {
        const newTap: TapListElement = shadow.querySelector('tap-list-element');
        console.log(shadow);
        assert.strictEqual('Tap List Stub', newTap.name);
        done();
    })
});

Here is the code for the original TapListElement

@customElement('tap-list-element')
export class TapListElement extends LitElement {
    private _menu: Menu;
    private _tapList: Section;
    private _bottleList: Section;

    @property()
    name: string = 'Tap List';

    @property({type: String, attribute: true})
    display: string;

    constructor() {
        super();
        super.connectedCallback();
    }
}

And here is the stub

@customElement('tap-list-element')
export class TapListElementStub extends LitElement {

    @property()
    name: string = 'Tap List Stub';

    @property({type: String, attribute: true})
    display: string;

    constructor() {
        super();
        super.connectedCallback();
    }
}

Apologies for any indentation typos.

When I tried the code above, I get the error that the web component with that name "tap-list-element" had already been registered. I tried removing the @customElement decorator in the TapListElementStub but then I get the error that an Illegal Constructor had been called. Has anyone tried stubbing litElement components? I'm coming from an Angular background. There the TestBed helper library sets up the module and you can substitute any component as long as the attributes remain the same and the name of the component in the decorator is the same.


Solution

  • The first error is likely caused by <tap-list-element> being defined twice in the two versions. This is not avoidable since as of now custom elements are not un-definable (and thus re-definable with a different class). Removing the registration call as you tried doesn't solve the problem because the component would still be created using the original class.

    The simplest solution would be to use a different tag name (even the Polymer docs you cited do this). If you really need the tag names to be the same then what I can suggest is to only replace some properties/methods of the original class. OpenWC and Modern Web testing guides have some parts (1, 2) dedicated to this.