Marko Jest ·
Jest Marko transformer and rendering test utility.
What is this?
Transformer and rendering test library for Marko 4 component with Jest & JSDOM.
- Renders Marko component on JSDOM
- Supports rendering and client-side behaviour testing
- Snapshot testing
- TypeScript support
Requirements
- Jest: 23.x
- Marko: ^4.9.0
Setup
-
Add
marko-jest
to your dev dependencies. You could do it byyarn add marko-jest --dev
ornpm install marko-jest --save-dev
. -
Register marko preprocessor/transformer on your Jest config. This allows Jest to process and compile Marko file. Add the following lines to the Jest transform section:
// package.json or jest config
Quick Start
These are a quick steps to test a Marko component with marko-jest:
-
Require marko-jest module and use the
init
function to initiate the Marko component you want to test. This is the way to 'require' Marko component on test files. -
The
init
function will returnrender
function which you can use to render the initiated Marko component.// __tests__/component.spec.js;// or const { init } = require('marko-jest');// init() requires full path to Marko componentconst componentPath = path;const render = ;; -
The
render
function returnsRenderResult
object which allows you to get the component instance. Use the component instance to access its properties (e.gel
,els
, orstate
) or methods (e.ggetEl()
,update()
,rerender()
) for testing.// __tests__/component.spec.js;const componentPath = path;const render = ;;
Component Rendering Test
One way to test a component is to test its generated HTML. You can access it from the RenderResult
object returned by the render
function.
You can use the following methods/property from the RenderResult
object:
- Property
component
: component instance. You can access the output HTML element using Marko component instance's properties (such asel
orels
), or methods (getEl(key)
orgetEls(key)
). - Property
container
: the test container element, which is a div element. Behind the scene, marko-jestrender
function automatically creates a test container and renders the component inside it. - Method
getNodes
: return the list of rendered HTML elements. Usually useful for snapshot testing (see next section).
Once you get the HTML element, you can use any native HTML methods to assert if a certain element or class is existed.
Examples:
// container; // component instance with property el; // component instance with getEl() if you have key attribute inside Marko template; // component instance with getEls();
Accessing Non-Element Nodes
Marko's getEl()
and getEls()
returns HTML elements only, which means it does not return any non-element nodes such as text and comment nodes. If you want to access element & non-elements (e.g for snapshot testing), you can use RenderResult getNodes()
which will return array of all Nodes, including HTML elements, text, and comment nodes.
;
A use case for this is you have a component which can render a text node without any HTML element as container
// span-or-text-component.marko<span body-only-if(!input.showSpan)> ${input.text}</span>
// test-span-or-text-component.spec.js;const render = ; ;
Snapshot testing
You can utilize Jest snapshot testing to test component rendering.
The RenderResult getNodes()
will return array of HTML elements which we can use for Jest snapshot feature.
Example:
// __tests__/component.spec.js;; const componentPath = path;const render = ; ;
Behaviour Testing
You can test component behaviour (e.g click handler) by triggering event though the HTML element.
Example on testing a toggle button:
// index.markoclass { onCreate() { this.state = { clicked: false }; } toggleButton() { this.state.clicked = !this.state.clicked; }} <button class="btn" on-click('toggleButton')> <span if(state.clicked)>DONE</span> <span else>Click me</span></button>
You can access the button element and trigger the click:
// __tests__/index.spec.js;; const componentPath = path;const render = ; ;
You can also combine it with snapshot testing:
;; const componentPath = path;const render = ; ;
TypeScript Support
marko-jest
module provides TypeScript type definition. Make sure you also install type definition for Marko by adding module @types/marko
to your project.
Shallow Rendering
marko-jest can do shallow rendering on external component. If you use external Marko component module/library (such as ebayui-core), you can exclude those components from being rendered deeply by adding the module name to Jest globals config taglibExcludePackages
. marko-jest will use Marko's taglibFinder.excludePackage()
to prevent any components from those modules to be rendered.
For example, if you want to do shallow rendering on all components from @ebay/ebayui-core
module, add the module name to Jest globals config:
// package.json
Now Marko Jest will render your Marko component:
// cta-component.marko<section> <ebay-button priority="primary" on-click('toggleButton')> PAY </ebay-button></section>
As:
PAY
Instead of
PAY
One of the advantages of shallow rendering is to isolate your unit test so you can focus on testing your component instead of the external ones. On the example above, if the ebay-button
implementation has changed (e.g css class name or new attribute added), your snapshot test will not failed.
Current Limitation of marko-jest shallow rendering
- The shallow rendering will affect ALL test suites, you cannot turn it on or off during runtime.
- You can only do shallow rendering on external modules. Unfortunately, you cannot do shallow rendering on component from the same project. The only workaround so far is to separate your UI component as external module (npm package) and consume it on your project.
marko-jest APIs
marko-jest API provides 2 high level functions: init
and cleanup
.
init(fullPathToMarkoComponent: string): InitResult
This is a way to 'require' Marko component on test file. It requires full path to Marko component.
At the moment, you can't easily require Marko component on Node.js with JSDOM. By default, when a Marko component is required on Node.js, you can only do server-side-only component. This means you can render the component as HTML but without any browser-side features such as render to virtual DOM, DOM event handling, or browser-side lifecycle.
init
function will 'trick' Marko to require a component on Node.js as if it's done on browser. Therefore, the required component will have all browser-side features, including component rendering.
The init
function will return an object InitResult
which has:
- property
componentClass: Component
, the Class of require/init-ed Marko Component. Quite useful if you want to spy on Marko component lifecycle method. - function
render(input: any): Promise<RenderResult>
: Asynchronously render the component using the given input. This will return a promise which will be resolved with an instance ofRenderResult
.
The RenderResult
is the result of component rendering which has:
- property
component: Component
: the rendered component instance. Use this instance to access any Marko component properties or methods. - property
container: HTMLElement
: the test container element, which is a div element. Behind the scene, marko-jestrender
function automatically creates a test container and renders the component inside it. - method
getNodes(): HTMLElement[]
: return the list of any rendered HTML elements. This method is better than Marko'sgetEl()
andgetEls()
which does not return any non-element nodes such as text and comment nodes. If you want to access element & non-elements (e.g for snapshot testing), you can usegetNodes()
which will return array of all Nodes, including HTML elements, text, and comment nodes.
cleanup(): void
Remove all test containers created by the render
function. Totally recommended to call cleanup on Jest afterEach
.
For more info about marko-jest API, you can check the TypeScript type definition here
Known Issues
- Failed rendering Marko component with custom transformer
- Limited support of shallow rendering, see Shallow Rendering above or https://github.com/abiyasa/marko-jest/issues/1
Roadmap
Planned new features and improvements:
- Better support of shallow and deep rendering.
Contributing
Contributing guidelines is still WIP but you're welcome to contribute by creating issues or Pull Request.
License
MIT