简化角度测试的强大工具
旁观者可以帮助您摆脱所有样板的咕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')。 。例如,宽度和高度设置为0 expect('element')。toBevisible();期望('input')。 component.Object).tobepartial({aproperty:'avalue'}); Expect('div')。tohavedescendant('。child'); Expect('div')。 '文本'});
使用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á |
该项目遵循全企业规范。欢迎任何形式的贡献!