簡化角度測試的強大工具
旁觀者可以幫助您擺脫所有樣板的咕unt作品,從而使您進行可讀,時尚和精簡的單元測試。
✅支持測試角組件,指令和服務
✅輕鬆查詢
✅清潔API用於觸發鍵盤/鼠標/觸摸事件
✅測試ng-content
✅自定義茉莉/開玩笑匹配器(Tohaveclass,Tobedisabled ..)
✅路由測試支持
✅HTTP測試支持
✅內置支持入門組件
✅對組件提供商的內置支持
✅自動嵌入式提供商
✅強烈鍵入
✅開玩笑的支持
贊助有助於持續開發和維護Ngneat圖書館。考慮要求您的公司贊助Ngneat作為其業務和應用程序開發的核心。
通過成為金牌贊助商來提升您的支持,並在排名前5個存儲庫中的Readme上亮著您的徽標。
通過成為金牌贊助商來增強您的支持,並在我們的讀者中出現在前三名存儲庫中的徽標中享受著焦點。

成為銅牌贊助商,並在Github上的Readme上獲取您的徽標。
特徵
目錄
安裝
NPM
紗
測試組件
嵌套的延遲視圖
字符串選擇器
類型選擇器
DOM選擇器
測試選擇元素
模擬組件
測試單個組件/指示角模塊
自定義活動
事件創建者
事件API
鍵盤助手
鼠標助手
查詢
可延期的觀點
主機測試
自定義主機組件
通過路由進行測試
觸發導航
使用RouterTestingModule集成測試
路由選項
測試指令
測試服務
其他選項
測試管道
使用自定義主機組件
嘲笑提供者
嘲笑依賴性
模擬構造函數依賴性
開玩笑的支持
用HTTP測試
全局注射
組件提供商
自定義匹配器
原理圖
默認原理圖集
工作觀眾和開玩笑樣本回購和業力比較
核心團隊
貢獻者
npm install @ngneat/spectator --save-dev
yarn add @ngneat/spectator --dev
通過使用createComponentFactory()函數創建組件工廠,傳遞要測試的組件類。 createComponentFactory()返回一個函數,該函數將在每個it塊中創建一個新的組件:
import {posetator,createComponentFactory}來自'@ngneat/spectator'; import {buttoncomponent} from'./ button.component'; describe'; describe('buttoncomponent',()=> {
令觀眾:旁觀者<buttonComponent>;
const createComponent = createComponentFactory(buttonComponent);
the each(()=> spectator = createComponent());
它('默認情況下應該有一個成功類',()=> {preenge(spectator.query('button'))。tohaveclass('success');
});
它('應該根據[className] input',()=> {spectator.setInput('className','danger'); Expect(spectator.query('button'')。 danger');期望(spectator.query ('button'))。否。
});}); createComponentFactory函數可以選擇採用以下選項,該選項擴展了基本的角度測試模塊選項:
const createComponent = createComponentFactory({{
組件:按鈕component,
進口:[],
提供者:[],
聲明:[],
入口處:[],
componentProviders:[],//覆蓋組件的提供商
ComponentViewProviders:[],//覆蓋組件的視圖提供商
超級模塊:[],//覆蓋模塊
超級組件:[],//在測試獨立組件的情況下,//覆蓋組件
超級方向:[],//在測試獨立指令的情況下覆蓋指令
覆蓋管:[],//在測試獨立管道的情況下覆蓋管道
模擬:[],//將自動嘲笑的提供商
componentMocks:[],//將自動嘲笑的組件提供商
ComponentViewProvidersMocks:[],//將自動模擬的組件視圖提供商
檢測:false,//默認為true
declarecomponent:false,//默認為true
禁用保存:false,//默認為true
淺:true,//默認為false
deferblockbehavior:deferblockbehavior //默認為deferblockbehavior.playthrough}); createComponent()函數(可選)採用以下選項:
它('應該...',()=> {
spectator = createComponent({// component inputsprops:{title:'click'},//覆蓋組件的提供者//請注意,您必須在`createComponentFactory` -providers oferne of creteconeComponentFactory` -providers''中:[] (默認為true)檢測:false
});
期待(spectator.query('button'))。通過在我們的createComponent()函數範圍內提供overrideComponents選項,我們可以定義覆蓋獨立組件的方式及其依賴項
@成分({
選擇器:`app-standalone with-import`,
模板:`<<div id =“ standalone”>帶有導入的獨立組件!</div> <app-standalOne-with依賴性> </app-standalone-with依賴性>`,
導入:[獨立的依賴性],
獨立:true,})導出class standalonewithimportscomponent {} @component({{
選擇器:`app-standalone-with依賴性',
模板:`<div id =“ standalOneWithDependentions”>依賴性!</div>`,
獨立:true,})導出類別andaloneComponentWithDependenty {
構造函數(公共查詢:queryservice){}}@component({
選擇器:`app-standalone-with依賴性',
模板:`<div id =“ standalOneWithDependentions”>具有覆蓋依賴項的獨立組件!</div>`,,
獨立:true,})導出類模擬compantalOnecomponentwithDepententy {
constructor(){}} it('應該...',()=> {
const spectator = create-hostFactory({component:standalonewithimportscomponent,模板:`<div> <div> <app-standalone-with-import> </app-standalone-with-import> </div> </div> </ div> <,overridecontents: {imports:[standalOneComponentWithDependentions]},add:{imports:[optastaloneComponentWithDependency]},},],],],],
});
期待(host.query('#standalone'))。tocontaintext('帶有導入的獨立組件!');
期待(host.query('#standalOneWithDependentions'))。tocontaintext('具有替代依賴關係的獨立組件!');}); createComponent()方法返回一個Spectator實例,該實例公開了以下API:
fixture - 經過測試的組件的固定裝置
component - 測試的組件的實例
element - 經過測試的組件的本地元素
debugElement - 經過測試的燈具的調試元素
flushEffects() - 為TestBed.flushEffects()提供包裝器
inject() - 為TestBed.inject()提供包裝器:
const service = spectator.Indject(queryService); const frofcomponentInjector = true; const service = spectator.Indject(queryService,fromcomponentInjector);
detectChanges() - 在測試元素/主機上運行檢測法:
spectator.detectchanges();
detectComponentChanges() - 在測試的組件上(不在host上)運行detectChanges 。當使用host時,您將需要此方法,並且經過測試的組件是onPush ,並且您希望強迫它運行更改檢測週期。
spectator.detectComponentChanges();
setInput() - 更改已測試組件的@input()的值。方法如果存在,則可以手動運行SimpleChanges ngOnChanges 。
它('應該...',()=> {
spectator.setInput('className','danger');
spectator.setInput({className:'danger'
});}); output - 返回已測試組件的可觀察的@output():
它('應該在單擊上發出$ event',()=> {
讓輸出;
spectator.output('click')。訂閱(結果=>(輸出=結果));
spectator.component.onclick({type:'click'});
期望(輸出).toequal({type:'click'});}); tick(millis?: number) - 運行fakeasync tick()函數,並致電detectChanges() :
它('應該與tick一起使用',fakeasync(()=> {
spectator = createComponent(ZippyComponent);
Spotator.component.update();
期待(spectator.component.updatedAsync).tobefalsy();
Spotator.tick(6000);
期待(spectator.component.updatedAsync).not.tobefalsy();}))每個事件都可以接受以下一個SpectatorElement :
類型旁觀=字符串|元素|調試| ElementRef |窗口|文檔| DOMSELECTOR;
如果未提供,默認元素將是正在測試的組件的主機元素。
click() - 觸發單擊事件:
spectator.click(spectorelement); spectator.click(bytext('element'));
blur() - 觸發一個模糊事件:
spectator.blur(spectorelement); spectator.blur(bytext('element'));
請注意,如果使用Jest框架,則Blur()僅在集中元素時起作用。細節。
focus() - 觸發焦點事件:
spectator.focus(spectorelement); spectator.focus(bytext('element'));
typeInElement() - 模擬用戶鍵入:
spectator.typeinelement(值,旁觀); spectator.typeinelement(value,bytext('element'));
dispatchMouseEvent() - 觸發鼠標事件:
spectator.dispatchmouseeevent(spectatorElement,'鼠標out'); spectator.dispatchmouseeevent(spectatorElement,'moolyout'),x,y,event); spectator.dispatchmouseevent(bytext(bytext(byement'') ('element'),'mouseout ',x,y,event);
dispatchKeyboardEvent() - 觸發鍵盤事件:
spectator.disPatchKeyBoardEvent(spectatorElement,'keyup','evave'); spectator.dispatchKeyboardEvent(spectatorElement,'keyup',{key:'evave:'evave',鍵代碼:27})spectator.dispatchkeyboardevent(' ','evave '); spectator.dispatchKeyboardEvent(bytext('element'),'keyup',{key:'evave:'evave',key代碼:27}) dispatchTouchEvent() - 觸發觸摸事件:
spectator.disPatchTouchEvent(spectorElement,type,x,y); spectator.dispatchTouchevent(bytext('element'),type,x,y);
您可以使用以下方法觸發自定義事件(@output()):
spectator.triggereventhandler(myChildComponent,“ mycustomevent','eventValue'); spectator.triggereventhandler(myChildComponent,'mycustomevent','eventValue','eventValue',{root:root}) ','eventValue'); spectator.triggereventhandler( 'app-child-component','mycustomevent','eventValue',{root:true});如果您想獨立於任何模板(例如在演示者服務中)測試事件,則可以在基礎事件創建者身上退縮。他們基本上提供了相同的簽名,而沒有前面的元素。
const keyboardEvent = createKeyBoardEvent('keyup','arrowdown'/ *,targetElement */); const mouseeevent = createMouseEevent('鼠標out'); const touchevent = createTouchevent('touchmove''; const faildevent; const fageevent = createfakeevent = createfakeEvent (createfakeevent('input');
spectator.keyboard.pressenter(); spectator.keyboard.pressescape(); spectator.keyboard.presstab(); spectator.kekeboard.kekeboard.press.pressbackspace(); spectator.keyboard.pold.press.presskey('a'' a'''a'' ctrl.a'); spectator.keyboard.presskey('ctrl.shift.a');
spectator.mouse.contextmenu('。selector'); spectator.mouse.dblclick('。selector');
請注意,以上方法中的每一種也將運行detectChanges() 。
觀眾API包含用於查詢DOM的方便方法,作為測試的一部分: query , queryAll , queryLast , queryHost和queryHostAll 。所有查詢方法都是多態性的,可讓您使用以下任何技術查詢。
傳遞字符串選擇器(以與使用jQuery或document.queryselector相同的樣式,以相同的樣式)查詢匹配DOM中該路徑的元素。此查詢的方法等效於Angular's.css謂詞。請注意,本機HTML元素將返回。例如:
//返回單個htmlelementspectator.query('div> ul.nav li:first-child'); // //返回所有匹配的htmlelementspectator.queryall的數組('div> ul.nav li') document contextspectator.query( 'div',{root:true}); spectator.query('app-child',{read:childServiceservice});將類型(例如組件,指令或提供商類)傳遞給查詢DOM中該類型的實例。這等同於Angular的By.directive謂詞。您可以選擇傳遞第二個參數,以從匹配元素的噴油器中讀取特定的注入令牌。例如:
//返回myComponent(如果存在)spectator.query.query(myComponent); //返回在dom(如果存在)spectator.query(mycomponent ,{read:someservice}); spectator.query(mycomponent,{read:elementRef} ); host.quelellast(childcomponent); host.queryall(childcomponent);旁觀者允許您使用受Dom-Test-library啟發的選擇器查詢元素。可用的選擇器是:
spectator.query(byplaceholder('請輸入您的電子郵件地址')); spectator.query(byvalue('by value')); spectator.query(bytitle('by title'by title')); spectator.query .query(byalttext('byalttext) alt text')); spectator.query(bylabel('by label')); spectator.query(bytext(bytext('by text')); spectator.query.query(bytext( 'by text',text',{selector:'#some。 selector'})); spectator.query(bytextContent('by text content',{selector:'#some .Selector'})); spectator.query. query(byrole('checkbox',ceckbox',{checked:true})); byText和byTextContent之間的區別在於,前者在嵌套元素中不匹配文本。
例如,在以下內容中,html byText('foobar', {selector: 'div'})與以下div匹配,但是byTextContent將:
<div> <span> foo </span> <span> bar </span> </div>
觀眾允許您查詢父元素中的嵌套元素。當您在頁面上有多個相同組件的實例並且想查詢特定的孩子中的孩子時,這很有用。父選擇器是用於查找父元素的字符串選擇器。父選擇器作為查詢方法的第二個參數傳遞。例如:
spectator.query(childComponent,{parenderselector:'#parent-component-1'}); spectator.queryall(childComponent,{parenderselector:'#parent-component-1'});觀眾允許您輕鬆測試<select></select>元素,並支持多選擇。
例子:
它('應該在Multi Select上設置正確的選項',()=> {
const select = spectator.query('#test-multi-select')作為htmlselectelement;
spectator.selectoption(select,['1','2']);
期望(select).tohaveselectedOptions(['1','2']);}); it('應該在標準選擇上設置正確的選項',()=> {
const select = spectator.query('#test-single-select')作為htmlselectelement;
Spectator.Selectoption(選擇,'1');
期望(select).tohaveselectedOptions('1');});它還允許您檢查change事件處理程序是否針對所選的每個項目正確起作用。如果您需要預先設置選擇而無需派遣更改事件,則可以禁用此功能。
API:
Spotator.Selectoption(SelectElement:HTMLSelectElement,選項:String | String [] | HtmloptionElement | htmloptionelement [],config:{emitevents:boolean} = {emitevents = {emitevents:true});例子:
它('應該派遣正確數量的更改事件',()=> {
const onChangespy = spyon(spectator.component,'handlechange');
const select = spectator.query('#test-onConChange-select')作為htmlSelectelement;
Spotator.Selectoption(select,['1','2'],{emitevents:true});
期望(select).tohaveselectections(['1','2']);
期待(onchangespy)。
const onChangespy = spyon(spectator.component,'handlechange');
const select = spectator.query('#test-onConChange-select')作為htmlSelectelement;
Spotator.Selectoption(select,['1','2'],{emitevents:false});
期望(select).tohaveselectections(['1','2']);
期待(onchangespy).not.tohavebeencalledtimes(2);});您還可以將HTMLOptionElement作為參數傳遞給selectOption和toHaveSelectedOptions Matcher。當您在<option>上使用[ngValue]綁定時,這特別有用:
它('傳遞元素時應該在單個選擇上設置正確的選項',()=> {
const select = spectator.query('#test-single-select-lement')作為htmlSelectelement;
Spotator.Selectoption(select,spectator.query(bytext('二'))作為htmloptionelement);
期望(select).tohaveselectedOptions(spectator.query(bytext('二'))作為htmloptionelement);});});如果您需要模擬組件,則可以使用NG Mocks庫。 ng-mocks不使用CUSTOM_ELEMENTS_SCHEMA ,它可能隱藏一些問題,而不會幫助您設置輸入,輸出等,而是為您自動模擬輸入,輸出等。
例子:
從'@ngneat/spectator'; import {create-hostfactory}; import {mockcomponent}從'ng-mocks''; import {foocomponent}來自'./ path/path/path/to/foo.component'pther/foo .concent creationhost = createHost = creationHostFactory({{{
組件:yourcomponenttotest,
聲明:[MockComponent(FooComponent)
]});可以通過在組件工廠的導入列表中與組件一起定義組件模塊以及組件中的組件模塊以及組件中聲明的組件(或指令)。例如:
const createComponent = createComponentFactory({{
組件:按鈕component,
導入:[ButtonComponentModule],});但是,當這樣使用時,觀眾內部將組件ButtonComponent component添加到內部創建的新模塊的聲明中。因此,您會看到以下錯誤:
Type ButtonComponent is part of the declarations of 2 modules [...]
可以告訴觀眾不要將組件添加到內部模塊的聲明中,而是使用明確定義的模塊。只需將工廠選項的declareComponent屬性設置為false :
const createComponent = createComponentFactory({{
組件:按鈕component,
導入:[ButtonComponentModule],
declarecomponent:false,});使用createiratextionFactory設置工廠選項的declareDirective屬性為false :
const creatextive = createiratextiveFactory({
組件:亮點組件,
導入:[righlightcomponentmodule],
聲明性:false,});觀眾提供了方便的API,以訪問可延期視圖( @defer {} )。
使用spectator.deferBlock(optionalIndex)方法訪問所需的延期塊。 optionalIndex參數是可選的,允許您指定要訪問的延期塊的索引。
訪問第一個延期塊:只需呼叫spectator.deferBlock()即可。
訪問後續的延期塊:使用相應的索引作為參數。例如, spectator.deferBlock(1)訪問第二個塊(基於零的索引)。
spectator.deferBlock(optionalIndex)返回四種用於渲染指定延期塊不同狀態的方法:
renderComplete() - 呈現延期塊的完整狀態。
renderPlaceholder() - 渲染還是致的還是致的還是致的還是致。
renderLoading() - 呈現延期塊的加載狀態。
renderError() - 呈現延期塊的誤差狀態。
例子:
@component({selector:'app-cmp',template:`@defer(在fiewport){<div>第一個defer block的完整狀態</div> <! - parent compart complete state->} @placeholder { <div>佔位符</div>}`,})class dummyComponent {} const createComponent = createComponentFactory({component:component:dummyComponent,deferblockbehavior:deferblockbehavior.manual.manual,}) > {// const constator = createComponent( );要訪問嵌套延期塊中的狀態,請調用從返回的塊狀態方法中調用deferBlock方法鏈。
示例:訪問嵌套的完整狀態:
//假設`spectator.deferblock(0).renderComplete()`呈現父的完整狀態defer defer blockconst parent consent parentcompleteState = await spectator.deferblock()。renderComplete()。 =等待parentcompleteState.renderComplete()。deferblock();
完整的示例:
@component({selector:'app-cmp',template:`@defer(on fiewport){<div>第一個defer block的完整狀態</div> <! - 父級完整狀態 - > @defer {< div>嵌套defer塊的完整狀態</div> <! - 嵌套的完整狀態- >}} @placeholder {<div>佔位符</div>}`,}`,})class dummycomponent {} const const cornsecomponent {component:dummyComponent,deferblockbehavior:deferblockbehavior.manual,}); it('應該呈現第一個嵌套的完整狀態',async(async()=> {//安排constator = constator = creativator = createComponent ()完整的const const parentcompleteState =等待spectator.deferblock()。renderComplete(); //渲染嵌套的狀態等待parentcompletestate.deferblock()。rendercomplete() defer塊');});用主機組件測試組件是一種更優雅,更強大的技術來測試您的組件。它基本上使您能夠以與編寫代碼相同的方式編寫測試。讓我們看看它的行動:
import {create -hostFactory,spectoratorHost}來自'@ngneat/cotterator'; descrip('zippyComponent',()=> {
令觀眾:SpotatorHost <ZippyComponent>;
const create -host = create -hostFactory(ZippyComponent);
它('應該從主機屬性顯示標題',()=> {spectator = create -host(`<zippy [title] =“ title”> </zippy>`,{hostprops:{title:'spectator:'spectator areaves'} });期望(spectator.query('。zippy__title'))。
});
它('如果打開',()=> {spectator = create -host(`<zippy title =“ zippy title”> zippy content </zippy>`); spectator.click('。zippy__title' );期望(spectator .query('。arrow'))。tohaveText('close'); Expect(spectator.query('。arrow'))。not.tohavetext('open');
});});主機方法返回一個SpectatorHost的實例,該實例通過以下其他API擴展Spectator :
hostFixture主機的固定裝置
hostComponent主機的組件實例
hostElement - 主機的本地元素
hostDebugElement主機的固定裝置元素
setHostInput更改主機組件的@Input()的值
queryHost閱讀有關觀眾中查詢的更多信息
queryHostAll閱讀有關觀眾中查詢的更多信息
使用setInput或props在組件上直接設置輸入時,使用主機組件進行測試。輸入應通過hostProps或setHostInput設置,並在模板中傳遞到您的組件。
有時,通過自己的主機實施是有幫助的。我們可以將自定義主機組件傳遞到createHostFactory()該組件將替換默認一個:
@component({selector:'custom-host',template:''})casture hostcomponent {
title ='custom hostcomponent';} descrip('與自定義主機組件',function(){
令觀眾:SpotatorHost <ZippyComponent,CustomHostComponent>;
const create -host = create -hostfactory({component:zippycomponent,host:customHostComponent
});
它('應該顯示主機組件標題',()=> {spectator = create -host(`<zippy [title] =“ title”> </zippy>`); enpert(spectator.query.query('。zippy__title ')) 。
});});對於使用路由的組件,有一個特殊的工廠可擴展默認的工廠,並提供一個固執的ActivatedRoute ,以便您可以配置其他路由選項。
描述('ProductDetailsComponent',()=> {
令觀眾:SpectatorTorting <ProductDetailsComponent>;
const createComponent = createroutingFactory({component:productDetailsComponent,params:{productid:'3'},data:{title:'一些標題'}
});
the each(()=> spectator = createComponent());
IT('應該顯示路由數據標題',()=> {Expect(spectator.query('。title'))。tohavetext('some title');
});
它('應該對路由更改反應',()=> {spectator.setrouteparam('propportId','5'); //您的測試在這裡...
});});SpectatorRouting API包括更新當前路由的方便方法:
接口SpectatorTorrouting <c>擴展了觀眾<c> {
/***通過更新參數,Queryparam和數據可觀察到的流來模擬路由導航。 */
TriggerNavigation(選項?:RouteOptions):void;
/***更新路由參數並觸發路線導航。 */
setRouteparam(名稱:字符串,值:字符串):void;
/***更新路由查詢參數並觸發路線導航。 */
setRoutequeryparam(名稱:字符串,值:字符串):void;
/***更新路由數據並觸發路線導航。 */
setRoutedata(name:string,value:any):void;
/***更新路由碎片並觸發路線導航。 */
setRoutefragment(fragment:string | null):void;
/***更新路線URL並觸發路線導航。 */
setRouteUrl(url:urlsegment []):void;}RouterTestingModule集成測試如果將stubsEnabled選項設置為false ,則可以使用來自Angular的RouterTestingModule進行真實的路由配置並設置集成測試。
請注意,這需要承諾解決。處理此問題的一種方法是使您的測試異步:
描述(“路由集成測試”,()=> {
const createComponent = createroutingFactory({component:mycomponent,nectrations:[其他component],stubsenabled:false,routes:[{path:'',component:mycomponent:mycomponent},{path:'foo'
});
它('應該使用路由器鏈接導航',async()=> {const spectator = createComponent(); //等待應許解決的承諾...等待spectator.fixter.fixture.whenstable(); //通過通過主張LocationExpect (spectator.Inkator(location).path())。tobe('/'); //單擊路由器linkspectator.click('。link-1'); //不要忘記等待等待有望解決...等待Spectator .fixture.Whenstable(); //通過斷言位置Expect(spectator.Inkator.Inking(location).path())。tobe('/foo')來測試新路線;
});});createRoutesFactory函數可以在默認的觀眾選項上採用以下選項:
params :在ActivatedRoute Stub中使用的初始參數
queryParams :在ActivatedRoute Stub中使用的初始查詢參數
data :在ActivatedRoute存根中使用的初始數據
fragment :用於ActivatedRoute Stub的初始片段
url :在ActivatedRoute存根中使用的初始URL段
root ActivatedRoute root的root的值
parent : ActivatedRoute存根的parent值的值
children : children的ActivatedRoute
firstChild : ActivatedRoute存根的firstChild的值
stubsEnabled (默認值: true ):啟用ActivatedRoute存根,如果設置為false則使用RouterTestingModule
routes :如果將stubsEnabled設置為false,則可以傳遞RouterTestingModule的Routes配置
有一個用於測試指令的特殊測試工廠。假設我們有以下指令:
@Directive({selector:'[rightlight]'})導出類突出顯示{
@HostBinding('style.background-color')背景彩色:字符串;
@HostListener('MouseOver')
onHover(){this.backgroundColor ='#000000';
}
@hostlistener('Mouseout')
onLeave(){this.backgroundColor ='#ffffff';
}}}讓我們看看如何輕鬆地與觀眾測試指令:
描述(“亮點”,()=> {
令觀眾:Spectatorcective <LightightDiractive>;
const creatextive = createirextiveFactory(亮點 - 驅動);
the each(()=> {spectator = createiractive(`<div亮點>測試突出顯示指令</div>`);
});
它('應該更改背景顏色',()=> {spectator.dispatchmouseeevent(spectator.element,'mouseover'); Expect(spectator.Element).tohavestyle({backgroundColor:'rgba:'rgba(0,0,0 ,0.1,0.1,0.1,0.1,0.1,0.1 )'}); spectator.dispatchmouseeevent(spectator.element,'meastOut'); endise(spectator.element).tohavestyle({backgroundColor:'#fff'});
});
它('應該獲取實例',()=> {const instance = spectator.diractive; guidef(instance).tobedefined();
});});直接使用setInput或props指令將輸入設置是不可能的。輸入應通過hostProps或setHostInput設置,並傳遞到模板中的指令。
以下示例顯示瞭如何用觀眾測試服務:
import {createServiceFactory,spectoratorService}來自'@ngneat/cotterator'; import {authService}來自'auth.service.ts'; condict('authservice',()=> {
令觀眾:SpectatorService <authservice>;
const createService = createServiceFactory(authService);
the each(()=> spectator = createService());
它(不應記錄在'中',()=> {Expectator.service.service.isloggedin())。tobefalsy();
});}); createService()函數返回具有以下屬性的SpectatorService :
service - 獲得服務的實例
inject() - Angular TestBed.inject()的代理
也可以通過選項傳遞對象。例如,在測試服務時,您通常想嘲笑其依賴關係,因為我們專注於正在測試的服務。
例如:
@Injectable()導出類authservice {
構造函數(私有日期服務:dateService){}
isloggedin(){if(this.dateservice.isexpired('timestamp')){return false;} return true;
}}}在這種情況下,我們可以模擬DateService依賴性。
import {createServiceFactory,spectoratorService}來自'@ngneat/cotterator'; import {authService}來自'auth.service.ts'; condict('authservice',()=> {
令觀眾:SpectatorService <authservice>;
const createService = createServiceFactory({服務:authservice,提供者:[],entryComponents:[],模擬:[dateService]
});
the each(()=> spectator = createService());
IT(“應在',()=> {const dateservice = spectator.Indect(dateService); dateservice.isexpired.and.returnvalue(false); Expect.servator.service.SISLOGGEDIN().tobetruthy(tobetruthy();
});});以下示例顯示瞭如何用觀眾測試管道:
import {spectatorPipe,createPipeFactory}來自'@ngneat/spectator'; import {statsservice} from'./stats.service'; import {sumpipe} from'./sum.pipe'; descripe'; describe'; {
令觀眾:SpotatorPipe <Sumpipe>;
const createPipe = createPipeFactory(sumpipe);
IT('應該總結給定的數字列表(模板)',()=> {coster = createPipe(`{{[1,2,3] | sum}}}`) ('6');
});
它('應該總結給定的數字列表(prop)',()=> {spectator = createPipe(`{{prop | sum}}`,{hostprops:{prop:[1,2,3]}} ) ;期望(spectator.element).tohavetext('6');
});
它('應該將求和委託給服務',()=> {const sum =()=> 42; const provider = {uffer:statsService,usevalue:{sum}}; spectator = createPipe(`{{prop { prop | sum}}`,{hostprops:{prop:[2,40]},提供者:[provider]}); Expect(spectator.element).tohavetext('42');
});}); createPipe()函數返回具有以下屬性的SpectatorPipe :
hostComponent主機組件的實例
debugElement - 主機組件周圍固定裝置的調試元素
element - 主機組件的本地元素
detectChanges() - Angular TestBed.fixture.detectChanges()的代理
inject() - Angular TestBed.inject()的代理
無法使用setInput或props直接在管道上設置輸入。輸入應通過hostProps或setHostInput設置,並在模板中傳遞到管道。
以下示例說明瞭如何使用自定義主機組件測試管道:
import {component,input}來自'@angular/core'; import {spectatorPipe,createPipeFactory}從'@ngneat/cotterator'; import {perateanpipe}從'./average.pipe'.import'; import'; import {statssservice } from'./stats .service';@component({{
模板:`<div> {{prop | avg}} </div>`})類CustomHostComponent {
@Input()公共道具:number [] = [1,2,3];} describe('paquipe -pipe',()=> {
令觀眾:SpotatorPipe <平均pipe>;
const createpipe = createPipeFactory({pipe:paquipepipe,host:customhostcomponent
});
它('應該計算給定數字列表的平均值',()=> {costator = createpipe(); precect(spectator.element).tohavetext('2');
});
IT(當數字列表為空時,應該導致0',()=> {spectator = createPipe({hostprops:{prop:[]}}); enpirence(spectator.element).tohavetext('0');
});
IT('應該將計算委託給服務',()=> {const avg =()=> 42; const provider = {upporter:statsservice,usevalue:{avg}}}; spectator = createpipe({提供者:{提供者:[提供者:[ ]});期望(spectator.element).tohavetext('42');
});});對於每個觀眾工廠,我們都可以輕鬆模擬任何提供商。
我們傳遞給mocks屬性的每項服務將使用mockProvider()函數模擬。 mockProvider()函數將每種方法轉換為茉莉花間諜。 (即jasmine.createSpy() )。
這是它暴露的一些方法:
dateservice.isexpired.and.callthrough(); dateservice.isexpired.and.callfake((()=> face> face); dateservice.isexpired.ister.and.throterror('error''); dateservice.isevice.isexpired. isexpired.andcallfake(andcallfake() ;
但是,如果您使用開玩笑作為測試框架,並且要使用其模擬機制,請從@ngneat/spectator/jest導入mockProvider() 。這將自動使用jest.fn()函數來創建開玩笑兼容模擬。
mockProvider()不包括屬性。如果您需要在模擬上擁有屬性,則可以使用第二參數:
const createService = createServiceFactory({{
服務:Authservice,
提供者:[MockProvider(其他服務,{name:'Martin',Emitter:new object(),MockedMethod:()=>'Mocked'})
],});如果組件依賴於在OnInit Lifececle方法中模擬的服務,則需要禁用更改檢測,直到注入服務後。
要配置這一點,請更改createComponent方法,以將detectChanges選項設置為false,然後在設置注入的服務後手動調用旁觀者的detectChanges 。
const createComponent = createComponentFactory({{
組件:weatherdashboardcomponent}); it('init on Init上調用天氣api',()=> {
const spectator = createComponent({dentectchanges:false)
});
const weatherservice = spectator.Indignt(weatherdataapi);
weatherservice.getWeatherdata.andreturn((oibweatherdata));
spectator.detectchanges();
期待(weatherservice.getWeatherdata).tohavebeencalled();});如果組件依賴於其構造函數中模擬的服務,則需要創建和配置模擬,並在創建組件時提供模擬。
const createComponent = createComponentFactory({{
組件:WeatherDashboardComponent}); it('應該在構造函數中調用天氣API',()=> {
const weatherService = createSpyObject(weatherdataapi);
weatherservice.getWeatherdata.andreturn((oibweatherdata));
spectator = createComponent({提供者:[{提供:weatherdataapi,useValue:weatherservice}]]
});
期待(weatherservice.getWeatherdata).tohavebeencalled();});默認情況下,觀眾使用茉莉花來創建間諜。如果您將JEST用作測試框架,則可以讓觀眾創建與Jest兼容的間諜。
只需從@ngneat/spectator/jest (而不是 @ngneat/coptator)導入以下功能之一,它將使用開玩笑而不是茉莉花。 createComponentFactory() , createHostFactory() , createServiceFactory() , createHttpFactory() , mockProvider() 。
import {createServiceFactory,spectatorservice}來自'@ngneat/spectator/jest'; import {authService} from'./auth.service'; import {dateservice {dateservice} from'./date./date.service'; date.service '; deScripe'; deScripe'juthScribe( => {
令觀眾:SpectatorService <authservice>;
const createService = createServiceFactory({服務:authservice,模擬:[dateservice]
});
the each(()=> spectator = createService());
它(不應登錄',()=> {const dateservice = spectator.ignds <dateservice>(dateservice); dateservice.isexpired.mockreturnvalue(true); Expperator.servator.Servator.Service.Service.isloggedIn() );
});
它(應登錄',()=> {const dateservice = spectator.ignds <dateservice>(dateservice); dateservice.isexpired.mockreturnvalue(false); precect.servator.service.service.service.isloggedin()。 ;
});});使用組件示意圖時,您可以指定--jest標誌以使用開玩笑的導入。 為了開玩笑地導入默認值,請更新angular.json :
“示意圖”:{“@ngneat/spectator:spectator-component”:{“ jest”:true
}
}觀眾製作使用Angular HTTP模塊的測試數據服務要容易得多。例如,假設您有三種方法的服務,一個執行get,一個帖子,一個執行並發請求:
導出類TodoSdatAservice {
構造函數(私有httpclient:httpclient){}
getTodos(){返回this.httpclient.get('api/todos');
}
PostTodo(ID:number){返回this.httpclient.post('api/todos',{id});
}
collecttodos(){return merge(this.httpclient.get('/api1/todos'),this.httpclient.get('/api2/todos'));
}}}上述服務的測試應該看起來像:
來自'@ngneat/spectator'; import {createhttpfactory,httpmethod}; import {todosdataservice} tododos-data.service'.descripe';
令觀眾:spectatorHttp <todosdataservice>;
const createHttp = createhttpfactory(todosdataservice);
the each(()=> spectator = createHttp());
它('可以測試httpclient.get',()=> {spectator.service.getTodos()。subscribe(); spectator.expectone('api/todos',httpmethod.get);
});
it('可以測試httpclient.post',()=> {spectator.service.posttodo(1).subscribe(); const req = spectator.expectone.expectone('api/todos',htttpmethod.post); Expect( req。 request.body ['id'])。to equal(1);
});
IT('可以測試當前HTTP請求',()=> {spectator.service.getTodos()。subscribe(); const reqs = spectator.expectConcurrent([{url:'/api1/todos',方法: },{ url:'/api2/todos',方法:httpmethod.get}]); spectator.flushall(reqs,[},[},{},{},{},{}]);
});});我們需要使用createHttpFactory()函數來創建HTTP工廠,並傳遞要測試的服務。 createHttpFactory()返回一個函數,該函數可以調用,以獲取具有以下屬性的spectoratorHttp實例:
controller - Angular HttpTestingController的代理
httpClient角度HttpClient的代理
service - 服務實例
inject() - Angular TestBed.inject()的代理
expectOne() - 期望提出一個與給定URL及其方法匹配的單個請求,然後返回其模擬請求
可以定義注射劑,這些注射將適用於每個測試,而無需在每個測試中重新分解它們:
// test.tsimport {defineglobalsInjections}來自'@ngneat/spectator'; import {cransperocomodule}來自'@ngneat/cransperoco'; defineglobalsInjections({{{
導入:[cranslocomodule],});請注意,必須在加載模塊之前調用defineGlobalsInjections() 。在默認的Angular test.ts這意味著在此行之前:
context.keys()。映射(上下文);
默認情況下,未觸摸原始組件提供商(例如@Component上的providers )。
但是,在大多數情況下,您希望在測試中訪問組件的提供商或用模擬替換它們。
例如:
@成分({
模板: '...',
提供商:[fooservice]})類foocomponent {
構造函數(私人fooservice:fooservice} {}
// ...}使用componentProviders替換FooService提供商:
const createComponent = createComponentFactory({{
組件:fooComponent,
componentProviders:[{fusis:fooservice,useValue:sometsing somewayse}
]))或使用componentMocks模擬服務:
const createComponent = createComponentFactory({{
組件:fooComponent,
componentMocks:[fooservice]});要訪問提供商,請使用fromComponentInjector參數從組件注射器獲取它:
spectator.Ink(fooservice,true)
以相同的方式,您還可以使用componentViewProviders和componentViewProvidersMocks覆蓋組件視圖提供商。
相同的規則也適用於使用directiveProviders和directiveMocks參數的指令。
期待('。zippy__content')。not.toexist();期望('。zippy__content')。 zippy'))。 )。 )。tocontainproperty({src:'myimg.jpg'}); //請注意,Tohaveclass僅按嚴格順序接受類。如果訂單無關緊要,請手動禁用嚴格的模式。expect('。zippy__content')。tohaveclass('class'');期望('。zippy__content')。 zippy__content')。否。 )。 tohaveclass('class-a ,class-b',{strict:false});期望('。zippy__content')。tohaveclass('class-b,class-a',class-a',{strict:false:false}); Expect(' 。 zippy__content')。 ,{strict:false}); //請注意,tohaveText僅尋找字符串的存在,而不是字符串完全相同。如果要驗證字符串完全相同,請使用tohaveexacttext.///請注意,如果要驗證字符串是完全相同但首先修剪的,請使用tohaveexacttrimmedtext.///請注意,如果您傳遞多個值,觀眾對每個數組元素的文本進行檢查。 ']);期望('。zippy__content')。tohavetext(((text)=> text.includes('..'..'));期望('。zippy__content') ').tocontaintext(['content a','content b']);期望('。zippy__content')。tohaveExactText('content'); etwork; etwork; etwork; content b']);期望('。zippy__content ')。tohaveexacttrimmedText('content');期望('。zippy__content')。 ).tohaveValue('value');期望(“。 ('.zippy__content')。 )。 .checkbox')。 。 For example width and height set to 0expect('element').toBeVisible();expect('input').toBeFocused();expect('div').toBeMatchedBy('.js-something');expect(spectator. component.object).toBePartial({ aProperty: 'aValue' });expect('div').toHaveDescendant('.child');expect('div').toHaveDescendantWithText({selector: '.child', text: '文字'});使用Angular CLI生成組件,服務和指令:(默認使用時)
成分
默認規格: ng g cs dashrized-name
帶有主機的規格: ng g cs dashrized-name --withHost=true
具有自定義主機的規格: ng g cs dashrized-name --withCustomHost=true
服務:
默認規格: ng g ss dashrized-name
測試HTTP數據服務的規格: ng g ss dashrized-name --isDataService=true
指示:
ng g ds dashrized-name
要將spectator用作Angular CLI項目中的默認集合,請將其添加到angular.json :
ng config cli.defaultCollection @ngneat/Spectator
spectator示意圖擴展了默認@schematics/angular收集。如果要設置諸如使用SCSS文件的組件之類的原理圖的默認設置,則必須從@schematics/angular將示意圖的名稱更改為 @ angular.json中的@ngneat/spectator :
“原理圖”:{“@ngneat/spectator:spectator-component”:{“ style”:“ scss”
}
}Angular Docs測試開發人員指南的業力示例已在觀眾和開玩笑中復制。 (為方便起見,這是業力示例的本地版本。)
可以在此處訪問觀眾和開玩笑版本。
Netanel的基礎 | Dirk Luijk | 本·埃利奧特(Ben Elliott) |
謝謝這些很棒的人(表情符號鑰匙):~~~~
I.西奈 ? ? | 瓦倫丁·布里亞科夫(Valentin Buryakov) ? | 本·格林豪斯(Ben Grynhaus) ? | 馬丁·諾(Martin Nut) | Lars Gyrup Brink Nielsen ? | 安德魯·格雷科夫(Andrew Grekov) ? | Jeroen Zwartepoorte |
奧利弗·施萊格爾(Oliver Schlegel) | 雷克斯 ? | Tchmura | Yoeri Nijs | 安德斯·斯卡比(Anders Skarby) | Gregor Woiwode | 亞歷山大·謝勒米特夫(Alexander Sheremetev) ? |
麥克風 | Mehmet Erim | 布雷特·埃克特(Brett Eckert) | Ismail Faizi | Maxime | 喬納森·邦納福伊(Jonathan Bonnefoy) | 柱渡輪 |
克里斯·庫珀 | 馬克·謝布(Marc Scheib) | DGSMITH2 | defdwardstech ? | Tamasfoldi ? | Paolo Caleffi | Toni Villena |
oday od | Guillaume de Jabrun | Anand Tiwary | Ales Doganoc | Zoltan | Vitalii Baziuk | clementlemarc-certua |
Yuriy Grunin | Andrey Chalkin | 史蒂文·哈里斯(Steven Harris) | 理查德·薩拉科比(Richard Sahrakorpi) | 多米尼克·克雷默(Dominik Kremer) | Mehmet Ozan Turhan | 弗拉德·拉什科(Vlad Lashko) |
威廉·蒂約德羅哈托(William Tjondrosuharto) | Chaz Gatian | Pavel Korobov | Enno Lohmann | Pawel Boguslawski | Tobias Wittwer | MateoTibaquirá |
該項目遵循全企業規範。歡迎任何形式的貢獻!