dojo/framework/src/testing/README.mdcommit 84e254725f41d60f624ab5ad38fe82e15b6348a2css
用於測試和斷言 Dojo 部件指望的虛擬 DOM 和行爲的簡單 API。node
測試git
當使用 @dojo/framework/testing
時,harness
是最重要的 API,主要用於設置每個測試並提供一個執行虛擬 DOM 斷言和交互的上下文。目的在於當更新 properties
或 children
和失效部件時,鏡像部件的核心行爲,而且不須要任何特殊或自定義邏輯。app
harness(renderFunction: () => WNode, customComparators?: CustomComparator[]): Harness;
renderFunction
: 返回被測部件 WNode 的函數customComparators
: 一組自定義比較器的描述符。每一個描述符提供一個比較器函數,用於比較經過 selector
和 property
定位的 properties
harness 函數返回的 Harness
對象提供了與被測部件交互的精簡 API:dom
Harness
ide
expect
: 對被測部件完整的渲染結果執行斷言expectPartial
: 對被測部件的一部分渲染結果執行斷言trigger
: 用於在被測部件的節點上觸發函數getRender
: 根據提供的索引返回對應的渲染器使用 @dojo/framework/widget-core
中的 w()
函數設置一個待測試的部件簡單且眼熟:函數式編程
class MyWidget extends WidgetBase<{ foo: string }> { protected render() { const { foo } = this.properties; return v('div', { foo }, this.children); } } const h = harness(() => w(MyWidget, { foo: 'bar' }, ['child']));
以下所示,harness 函數也支持 tsx
語法。README 文檔中其他示例使用編程式的 w()
API,在 unit tests 中可查看更多 tsx
示例。
const h = harness(() => <MyWidget foo="bar">child</MyWidget>);
renderFunction
是延遲執行的,因此可在斷言之間包含額外的邏輯來操做部件的 properties
和 children
。
let foo = 'bar'; const h = harness(() => { return w(MyWidget, { foo }, [ 'child' ])); }; h.expect(/** assertion that includes bar **/); // update the property that is passed to the widget foo = 'foo'; h.expect(/** assertion that includes foo **/)
在某些狀況下,咱們在測試期間沒法得知屬性的確切值,因此須要使用自定義比較描述符。
描述符中有一個定位要檢查的虛擬節點的 selector
,一個應用自定義比較的屬性名和一個接收實際值並返回一個 boolean 類型斷言結果的比較器函數。
const compareId = { selector: '*', // all nodes property: 'id', comparator: (value: any) => typeof value === 'string' // checks the property value is a string }; const h = harness(() => w(MyWidget, {}), [compareId]);
對於全部的斷言,返回的 harness
API 將只對 id
屬性使用 comparator
進行測試,而不是標準的相等測試。
harness
API 支持 CSS style 選擇器概念,來定位要斷言和操做的虛擬 DOM 中的節點。查看 支持的選擇器的完整列表 以瞭解更多信息。
除了標準 API 以外還提供:
key
屬性簡寫爲 @
符號.
來定位樣式類時,使用 classes
屬性而不是 class
屬性harness.expect
測試中最多見的需求是斷言部件的 render
函數的輸出結構。expect
接收一個返回被測部件指望的渲染結果的函數做爲參數。
API
expect(expectedRenderFunction: () => DNode | DNode[], actualRenderFunction?: () => DNode | DNode[]);
expectedRenderFunction
: 返回查詢節點指望的 DNode
結構的函數actualRenderFunction
: 一個可選函數,返回被斷言的實際 DNode
結構h.expect(() => v('div', { key: 'foo' }, [w(Widget, { key: 'child-widget' }), 'text node', v('span', { classes: ['class'] })]) );
expect
也能夠接收第二個可選參數,返回要斷言的渲染結果的函數。
h.expect(() => v('div', { key: 'foo' }), () => v('div', { key: 'foo' }));
若是實際的渲染輸出和指望的渲染輸出不一樣,就會拋出一個異常,並使用結構化的可視方法,用 (A)
(實際值)和 (E)
(指望值)指出全部不一樣點。
斷言的錯誤輸出示例:
v("div", { "classes": [ "root", (A) "other" (E) "another" ], "onclick": "function" }, [ v("span", { "classes": "span", "id": "random-id", "key": "label", "onclick": "function", "style": "width: 100px" }, [ "hello 0" ]) w(ChildWidget, { "id": "random-id", "key": "widget" }) w("registry-item", { "id": true, "key": "registry" }) ])
harness.expectPartial
expectPartial
根據 selector
斷言部件的部分渲染輸出。
API
expectPartial(selector: string, expectedRenderFunction: () => DNode | DNode[]);
selector
: 用於查找目標節點的選擇器expectedRenderFunction
: 返回查詢節點指望的 DNode
結構的函數actualRenderFunction
: 一個可選函數,返回被斷言的實際 DNode
結構示例:
h.expectPartial('@child-widget', () => w(Widget, { key: 'child-widget' }));
harness.trigger
harness.trigger()
在 selector
定位的節點上調用 name
指定的函數。
interface FunctionalSelector { (node: VNode | WNode): undefined | Function; } trigger(selector: string, functionSelector: string | FunctionalSelector, ...args: any[]): any;
selector
: 用於查找目標節點的選擇器functionSelector
: 要麼是從節點的屬性中找到的被調用的函數名,或者是從節點的屬性中返回一個函數的函數選擇器args
: 爲定位到的函數傳入的參數若是有返回結果,則返回的是被觸發函數的結果。
用法示例:
// calls the `onclick` function on the first node with a key of `foo` h.trigger('@foo', 'onclick');
// calls the `customFunction` function on the first node with a key of `bar` with an argument of `100` // and receives the result of the triggered function const result = h.trigger('@bar', 'customFunction', 100);
functionalSelector
返回部件屬性中的函數。函數也會被觸發,與使用普通字符串 functionSelector
的方式相同。
用法示例:
假定有以下 VDOM 樹結構,
v(Toolbar, { key: 'toolbar', buttons: [ { icon: 'save', onClick: () => this._onSave() }, { icon: 'cancel', onClick: () => this._onCancel() } ] });
而且你想觸發 save 按鈕的 onClick
函數。
h.trigger('@buttons', (renderResult: DNode<Toolbar>) => { return renderResult.properties.buttons[0].onClick; });
harness.getRender
harness.getRender()
返回索引指定的渲染器,若是沒有提供索引則返回最後一個渲染器。
getRender(index?: number);
index
: 要返回的渲染器的索引用法示例:
// Returns the result of the last render const render = h.getRender();
// Returns the result of the render for the index provided h.getRender(1);
斷言模板(assertion template)容許你構建一個傳入 h.expect()
的指望渲染函數。斷言模板背後的思想來自常常要斷言整個渲染輸出,並須要修改斷言的某些部分。
要使用斷言模板,首先須要導入模塊:
import assertionTemplate from '@dojo/framework/testing/assertionTemplate';
而後,在你的測試中,你能夠編寫一個基本斷言,它是部件的默認渲染狀態:
假定有如下部件:
class NumberWidget extends WidgetBase<{ num?: number }> { protected render() { const { num } = this.properties; const message = num === undefined ? 'no number passed' : `the number ${num}`; return v('div', [v('span', [message])]); } }
基本斷言以下所示:
const baseAssertion = assertionTemplate(() => { return v('div', [ v('span', { '~key': 'message' }, [ 'no number passed' ]); ]); });
在測試中這樣寫:
it('should render no number passed when no number is passed as a property', () => { const h = harness(() => w(NumberWidget, {})); h.expect(baseAssertion); });
如今咱們看看,爲 NumberWidget
部件傳入 num
屬性後,如何測試數據結果:
it('should render the number when a number is passed as a property', () => { const numberAssertion = baseAssertion.setChildren('~message', ['the number 5']); const h = harness(() => w(NumberWidget, { num: 5 })); h.expect(numberAssertion); });
這裏,咱們使用 baseAssertion 的 setChildren()
api,而後咱們使用特殊的 ~
選擇器來定位 key 值爲 ~message
的節點。~key
屬性(使用 tsx 的模板中是 assertion-key
)是斷言模板的一個特殊屬性,在斷言時會被刪除,所以在匹配渲染結構時不會顯示出來。此功能容許你修飾斷言模板,以便能簡單的選擇節點,而不須要擴展實際的部件渲染函數。一旦咱們獲取到 message
節點,咱們就能夠將其子節點設置爲指望的 the number 5
,而後在 h.expect
中使用生成的模板。須要注意的是,斷言模板在設置值時老是返回一個新的斷言模板,這能夠確保你不會意外修改現有模板(可能致使其餘測試失敗),並容許你基於新模板,增量逐層構建出新的模板。
斷言模板具備如下 API:
setChildren(selector: string, children: DNode[], type?: 'prepend' | 'replace' | 'append'): AssertionTemplateResult; setProperty(selector: string, property: string, value: any): AssertionTemplateResult; getChildren(selector: string): DNode[]; getProperty(selector: string, property: string): any;