# 🦉 Testing and Debugging Owl components 🦉 ## Content - [Overview](#overview) - [Unit Tests](#unit-tests) - [Debugging](#debugging) ## Overview It is a good practice to test applications and components to ensure that they behave as expected. There are many ways to test a user interface: manual testing, integration testing, unit testing, ... In this section, we will discuss how to write unit tests for components, and how to debug them if necessary. ## Unit Tests Writing unit tests for Owl components really depends on the testing framework used in a project. But usually, it involves the following steps: - create a test file: for example `SomeComponent.test.js`, - in that file, import the code for `SomeComponent`, - add a test case: - create a real DOM element to use as test fixture, - create a test environment - create an instance of `SomeComponent`, mount it to the fixture - interact with the component and assert some properties. To help with this, it is useful to have a `helper.js` file that contains some common utility functions: ```js export function makeTestFixture() { let fixture = document.createElement("div"); document.body.appendChild(fixture); return fixture; } export function nextTick() { let requestAnimationFrame = owl.Component.scheduler.requestAnimationFrame; return new Promise(function(resolve) { setTimeout(() => requestAnimationFrame(() => resolve())); }); } export function makeTestEnv() { // application specific. It needs a way to load actual templates const templates = ...; return { qweb: new QWeb(templates), ..., // each service can be mocked here }; } ``` With such a file, a typical test suite for Jest will look like this: ```js // in SomeComponent.test.js import { SomeComponent } from "../../src/ui/SomeComponent"; import { nextTick, makeTestFixture, makeTestEnv} from '../helpers'; //------------------------------------------------------------------------------ // Setup //------------------------------------------------------------------------------ let fixture: HTMLElement; let env: Env; beforeEach(() => { fixture = makeTestFixture(); env = makeTestEnv(); // we set here the default environment for each component created in the test Component.env = env; }); afterEach(() => { fixture.remove(); }); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("SomeComponent", () => { test("component behaves as expected", async () => { const props = {...}; // depends on the component const comp = new SomeComponent(null, props); await comp.mount(fixture); // do some assertions expect(...).toBe(...); fixture.querySelector('button').click(); await nextTick(); // some other assertions expect(...).toBe(...); }); }); ``` Note that Owl does wait for the next animation frame to actually update the DOM. This is why it is necessary to wait with the `nextTick` (or other methods) to make sure that the DOM is up-to-date. It is sometimes useful to wait until Owl is completely done updating components (in particular, if we have a highly concurrent user interface). This next helper simply polls every 20ms the internal Owl task queue and returns a promise which resolves when it is empty: ```js function afterUpdates() { return new Promise((resolve, reject) => { let timer = setTimeout(poll, 20); let counter = 0; function poll() { counter++; if (owl.Component.scheduler.tasks.length) { if (counter > 10) { reject(new Error("timeout")); } else { timer = setTimeout(poll); } } else { resolve(); } } }); } ``` ## Debugging Non trivial applications become quickly more difficult to understand. It is then useful to have a solid understanding of what is going on. To help with that, the following code can simply be copy/pasted in an application. Once it is executed, it will log a lot of information on each component main hooks. ```js let current; Object.defineProperty(owl.Component, "current", { get() { return current; }, set(comp) { current = comp; const name = comp.constructor.name; let __owl__; Object.defineProperty(current, "__owl__", { get() { return __owl__; }, set(val) { __owl__ = val; debugComponent(comp, name, __owl__.id); } }); } }); function toStr(props) { let str = JSON.stringify(props || {}); if (str.length > 200) { str = str.slice(0, 200) + "..."; } return str; } function debugComponent(component, name, id) { console.log(`[DEBUG] constructor ${name}, props=${toStr(component.props)}`); owl.hooks.onWillStart(() => { console.log(`[DEBUG] willStart: '${name}'`); }); owl.hooks.onMounted(() => { console.log(`[DEBUG] mounted: '${name}'`); }); owl.hooks.onWillUpdateProps(nextProps => { console.log(`[DEBUG] willUpdateProps: '${name} nextprops=${toStr(nextProps)}`); }); owl.hooks.onWillPatch(() => { console.log(`[DEBUG] willPatch: '${name}'`); }); owl.hooks.onPatched(() => { console.log(`[DEBUG] patched: '${name}'`); }); owl.hooks.onWillUnmount(() => { console.log(`[DEBUG] willUnmount: '${name}'`); }); const __render = component.__render.bind(component); component.__render = function(...args) { console.log(`[DEBUG] rendering template: '${name}'`); __render(...args); }; const render = component.render.bind(component); component.render = function(...args) { console.log(`[DEBUG] render: '${name}'`); return render(...args); }; const mount = component.mount.bind(component); component.mount = function(...args) { console.log(`[DEBUG] mount: '${name}'`); return mount(...args); }; } ``` Note that it is certainly useful to run this code at some point in an application, just to get a feel of what each user action implies, for the framework.