Unit Testing Stencil One

Tally Barak
8 min readJun 7, 2019

--

Photo by Markus Spiske on Unsplash

This is an update to my previous article on Testing StencilJS adopted for Stencil One (stencil 1.0) unit testing. Scroll to the end of this article to read a short opinionated history of Stencil unit testing evolution.

In this article, as in the previous one, I will assume you are familiar with unit testing basics as well as with Jest, and are looking to implement them with Stencil. Buckle on!

Configuration

Although Stencil is provided a command line to run unit testing, I prefer to have a standard jest config. Stencil has a set of presets that are useful for a simple project and can be extended in the jest.config:

You will also need to install the following dependencies:

npm install — save jest @types/jest jest-cli

Now you can add npm scripts to your package.json:

“test”: “jest”,“test:watch”: “jest — watch”

And you are good to go running jest in single on watch mode. All of jest CLI options are there.

Component Instance

Component instance tests are useful if you want to test the logic of some method. The following example is testing the format function on an instance of the component:

Fun fact: If the internal method is defined as private, you will still be able to test it but typescript will yell at you that:

Property ‘doNothing’ is private and only accessible within class ‘MyInstance’.

Rendering

This is nice, but it is not so much of testing. The main goal of unit testing is to verify that the component is actually rendering and behaves as needed. For this purpose Stencil has created a MockWindow utility which emulates the browser behavior for rendering components.

Let’s write a simple component (and by simple I mean really simple!):

To test this component we will need to render it on the mockWindow:

newSpecPage is the mockWindow initializing function provided by Stencil. The newSpecPage can receive multiple options that can determine its behavior (hopefully the stencil team will document the less obvious one). The important parameters are:

  • components: an array of stencil components that will be known to the mock Page. In simple words: those are the components that will be added with defineCustomElement on the window. Any component that will be added in the array will be rendered. Otherwise, only the tag name will be displayed.
  • html: an html string to set as the content of the page
  • supportShadowDom: determine if rendering will be performed with shadow dom. In the example above, the only visible change will be that the equalHTML will not contain the <mock: shadow root> elements.

There are additional option for RTL, server rendering and more.

The newSpecPage function returns a page object that has the following properties:

  • win: a browser’s window like object.
  • doc: is a browser’s document like object
  • root: is an HTML Element of HTML created inside the mockWindow
  • rootInstance: an instance of the top of the top level element, so functions may be triggered on it.

Page object has also important methods :

  • setContent: Used to change the html content after the mockWindow is generated.
  • waitForChanges: trigger all changes that occurred as result of changes to the object. So this is required every time changes need to be tested.

Stencil also provides some dedicated matchers, such as the toEqualHTML shown above. Stencil code has some documentation on those matchers that you can find here.

If you have been working with Jest before, you are likely in deep love with snapshots expectations. Luckily, MockHTML elements (such as doc and root) can be snapshot and the resulting snapshot will be the same as the equal HTML.

Querying HTML

The root and doc properties of the page object can be used to query the DOM. The querying varies between shadow and non shadow elements and if the rendering was done with or without shadow DOM support.

Elements that based on shadow DOM components have a shadowRoot property that can be used to query sub selectors.

Let’s extend the rendering of the html of the above component to contain a slot. The new render functions will now return:

<div>   <p class="nice">My name is {this.last} {this.first}</p>   <slot/></div>

The relevant tests are here:

Querying the elements (in this case root element) varies if the rendering with or without shadow DOM, as you can see bellow. The first test is rendering with shadow Dom so the selector need to access the shadowRoot property. The second test is bypassing shadow Dom so the whole DOM tree is available for querying.

Complex Properties

It is most likely that your stencil component will get props which are not pure scalars but are complex properties such as arrays or Objects.

In this case, you cannot set the properties directly on the html and the values need to set in code. Let’s look at a sample component:

This component changes the values in 2 cases:

  • on watching the values
  • Initially when the component will load.

Here are 2 tests for testing the 2 different cases:

The first test will not contain the data when it is rendered, and the data will change only on the page.waitForChanges().

But if we want to test the componentWillLoad with the rendered data, we will need to create the element via code, set the values and then append it to the code. Note that the HTML rendered cannot be empty, so we create a dummy div that will be generated on the page, and later we will add our component to the page.

Methods and Events

Let’s now test events and methods. You will need to use mocks and spy which are documented in detail in Jest documentation. Once again, let’s create a component that has an event and a method that can be called:

This is a dumb component, but it will serve its purpose of demonstrating our tests:

The first test is attaching an event listener to the window and is expected to been called, as it is emitted by the component when the button is clicked.

The second test is triggering the method and then waits for the changes. It will then validate that the anticipated changes (button text was modified) has happened. Note the await for the page changes.

Input

When testing fields input, it is sometimes required to trigger synthetic events to complete the events that would normally triggered by the browser.

Let’s look at this component that needs to be tested:

2 things happen on the input element: it will trigger a local function onInputChanged on the input event, and it will set a reference to the input element in the inputElement variable using the ref property. This can be useful if you want to dynamically manipulate the DOM. Once the input is changed, a higher level event is being emitted with the new value. The reasoning is that this event will only be triggered only once the input data is finalized and valid, and this is the only event that other components need to be aware of.

And the testing spec would look like that:

We define a spy function that will listen to the emitted event. We set the value of the input field and trigger an input event by creating a synthetic event (new Event(‘input’). Then we need to wait for the changes to apply, and we expect the input spy to be called with the value we emitted.

Fetch

If you are using fetch and you want to mock the calls, here is a suggestion. The component is fetching the language JSON upon changes to the language prop.

And testing it require spying on the global fetch attribute:

Summary

If you are here, you probably know the importance of testing in software development. I hope this article will help you tackle some of the issues you may encounter when testing Stencil components.

You can find the code on this repo: https://github.com/Tallyb/stencil-one. The repo is constantly being updated, and has some additional examples not included in this article. If you have additional tips, do not hesitate to PR!

>>>> Brief opinionated history about testing in Stencil

When Stencil was initially introduced, it came with testing batteries included. The stencil team quite rightfully chose Jest as its test runner, and opted for the JSDom that was included with Jest as a rendering engine for the components. A TestWindow utility was introduced that wrapped JSDom and initialized it with Stencil components.

Sadly, this turned out not to be a smooth path. JSDom is mocking browser APIs in node which makes it fast and reliable, but it also means that not everything you would expect in a browser environment exists. While JSDom is constantly updated according to browser specs, it is not always catching up. A main issue was still an open one is support for web components APIs. And since stencil is a, well, web components compiler, this problem became a real hurdle in testing with JSDom.

In Stencil 0.13 the stencil team decided to stop the struggle and removed the JSDom implementation. It was about the same time that various connectors were introduced to use Puppeteer with Jest. Puppeteer is node APIs that are wrapping chrome headless, so testing can happen on a real browser.

But testing in real browser is relatively slow and brittle. Not exactly what you expect from unit tests. So the stencil team promoted the puppeteer tests as e2e tests. Unit testing were left to a bare minimum of only calling instance method and with no rendering mechanism at all. The stencil documentation promote this approach:

There are countless philosophies on how testing should be done, and what should be considered a unit test, end-to-end test or even integration tests. To simplify it all, Stencil breaks it down to so developers have a defined description of when to use each type of testing.

While this is a valid statement, reality is somewhat different. Other framework support rendering in unit testing: Angular has the testBed for doing that and React uses, well — jest and JSDom for testing single component rendering using the ReactDom.render() method.

In addition to that, the stencil 0.x versions had no easy way to compile a single component and use it in a browser. It was all or nothing. So stencil unit tests were quite broken between 0.13 and 0.18.

Towards 1.0, the stencil team also realized that Stencil has a missing piece in the puzzle in order to deliver its mission: fast efficient unit tests that can simulate rendering but are not depended on compiling the whole suite of components and launching full blown browser accessing a designated url.

And yes, the good news arrived in Stencil 1.0. Stencil compiler improved to be able to compile and render a single component, and a new testing utility was introduced — MockWindow.

MockWindow is a mocked browser APIs but with web components first approach. Tests are back again! Stencil is still supporting e2e tests with puppeteer, but the fun of writing decent unit tests is restored.

>>>>> End of Rant

--

--

Tally Barak
Tally Barak

Written by Tally Barak

If you can’t explain it simply, you don’t understand it well enough.” — Einstein

Responses (4)