Skip to main content
Version: v4.18

Unit Testing

Stencil makes it easy to unit test components and app utility functions using Jest. Unit tests validate the code in isolation. Well written tests are fast, repeatable, and easy to reason about.

To run unit tests, run stencil test --spec. Files ending in .spec.ts will be executed.

newSpecPage()

In order to unit test a component as rendered HTML, tests can use newSpecPage() imported from @stencil/core/testing. This testing utility method is similar to newE2EPage(), however, newSpecPage() is much faster since it does not require a full Puppeteer instance to be running. Please see the newE2EPage() docs for more information about complete End-to-end testing with Puppeteer.

Below is a simple example where newSpecPage() is given one component class which was imported, and the initial HTML to use for the test. In this example, when the component MyCmp renders it sets its text content as "Success!". The matcher toEqualHtml() is then used to ensure the component renders as expected.

import { newSpecPage } from '@stencil/core/testing';
import { MyCmp } from '../my-cmp';

it('should render my component', async () => {
const page = await newSpecPage({
components: [MyCmp],
html: `<my-cmp></my-cmp>`,
});
expect(page.root).toEqualHtml(`
<my-cmp>Success!</my-cmp>
`);
});

The example below uses the template option to test the component

mycmp.spec.tsx
// Since the 'template' argument to `newSpecPage` is using jsx syntax, this should be in a .tsx file.
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { MyCmp } from '../my-cmp';

it('should render my component', async () => {
const greeting = 'Hello World';
const page = await newSpecPage({
components: [MyCmp],
template: () => (<my-cmp greeting={greeting}></my-cmp>),
});
expect(page.root).toEqualHtml(`
<my-cmp>Hello World</my-cmp>
`);
});

You render functional components without defining them in the components list, e.g.:

mycmp.spec.tsx
import { Fragment, h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
import { MyFunctionalComponent } from '../my-functional-cmp';

it('should render my component', async () => {
/**
* or define functional components directly in your test
*/
const AnotherFunctionalComponent = (props: any, children: typeof Fragment) => (
<section>{children}</section>
);

const page = await newSpecPage({
components: [],
template: () => (
<AnotherFunctionalComponent>
<MyFunctionalComponent>Hello World!</MyFunctionalComponent>
</AnotherFunctionalComponent>
),
});
expect(page.root).toEqualHtml(`
<section>
<div>
Hello World!
</div>
</section>
`);
});
note

Declaring Stencil class components directly within class files is not supported. If you need such a component for your tests, we recommend to put this into an extra fixture file.

Spec Page Options

The newSpecPage(options) method takes an options argument to help write tests:

OptionDescription
componentsAn array of components to test. Component classes can be imported into the spec file, then their reference should be added to the component array in order to be used throughout the test. Child components should also be imported. For example, if component Foo uses component Bar then both Foo and Bar should be imported Required
htmlThe initial HTML used to generate the test. This can be useful to construct a collection of components working together, and assign HTML attributes. This value sets the mocked document.body.innerHTML.
templateThe initial JSX used to generate the test. Use template when you want to initialize a component using their properties, instead of their HTML attributes. It will render the specified template (JSX) into document.body.
autoApplyChangesBy default, any changes to component properties and attributes must call page.waitForChanges() in order to test the updates. As an option, autoApplyChanges continuously flushes the queue in the background. Defaults to false
cookieSets the mocked document.cookie.
directionSets the mocked dir attribute on <html>.
languageSets the mocked lang attribute on <html>.
referrerSets the mocked document.referrer.
supportsShadowDomManually set if the mocked document supports Shadow DOM or not. Defaults to true
userAgentSets the mocked navigator.userAgent.
urlSets the mocked browser's location.href.

Spec Page Results

The returned "page" object from newSpecPage() contains the initial results from the first render. It's also important to note that the returned page result is a Promise, so for convenience it's recommended to use async/await.

The most useful property on the page results would be root, which is for convenience to find the first root component in the document. For example, if a component is nested in many <div> elements, the root property goes directly to the component being tested in order to skip the query selector boilerplate code.

ResultDescription
bodyMocked testing document.body.
docMocked testing document.
rootThe first component found within the mocked document.body. If a component isn't found, then it'll return document.body.firstElementChild.
rootInstanceSimilar to root, except returns the component instance. If a root component was not found it'll return null.
setContent(html)Convenience function to set document.body.innerHTML and waitForChanges(). Function argument should be an html string.
waitForChanges()After changes have been made to a component, such as a update to a property or attribute, the test page does not automatically apply the changes. In order to wait for, and apply the update, call await page.waitForChanges().
winMocked testing window.

Testing Component Class Logic

For simple logic only testing, unit tests can instantiate a component by importing the class and constructing it manually. Since Stencil components are plain JavaScript objects, you can create a new component and execute its methods directly.

import { MyToggle } from '../my-toggle.tsx';

it('should toggle the checked property', () => {
const toggle = new MyToggle();

expect(toggle.checked).toBe(false);

toggle.someMethod();

expect(toggle.checked).toBe(true);
});