Esta página descreve os recursos de teste do Angular mais úteis.
As utilitários de teste do Angular incluem o TestBed, o ComponentFixture e um punhado de funções que controlam o ambiente de teste.
As classes TestBed e ComponentFixture são cobertas separadamente.
Aqui está um resumo das funções autônomas, em ordem de utilidade provável:
| Função | Detalhes |
|---|---|
waitForAsync |
Executa o corpo de uma função de teste (it) ou configuração (beforeEach) dentro de uma zona de teste assíncrona especial. Veja waitForAsync. |
fakeAsync |
Executa o corpo de um teste (it) dentro de uma zona de teste fakeAsync especial, permitindo um estilo de codificação de fluxo de controle linear. Veja fakeAsync. |
tick |
Simula a passagem do tempo e a conclusão de atividades assíncronas pendentes liberando ambas as filas de timer e micro-task dentro da zona de teste fakeAsync. O leitor curioso e dedicado pode gostar deste longo post de blog, "Tasks, microtasks, queues and schedules". Aceita um argumento opcional que move o relógio virtual para frente pelo número especificado de milissegundos, limpando atividades assíncronas agendadas dentro desse período de tempo. Veja tick. |
inject |
Injeta um ou mais services do injetor TestBed atual em uma função de teste. Não pode injetar um service fornecido pelo próprio component. Veja discussão do debugElement.injector. |
discardPeriodicTasks |
Quando um teste fakeAsync() termina com tasks de evento de timer pendentes (callbacks setTimeOut e setInterval enfileirados), o teste falha com uma mensagem de erro clara. Em geral, um teste deve terminar sem tasks enfileiradas. Quando tasks de timer pendentes são esperadas, chame discardPeriodicTasks para liberar a fila de task e evitar o erro. |
flushMicrotasks |
Quando um teste fakeAsync() termina com micro-tasks pendentes como promises não resolvidas, o teste falha com uma mensagem de erro clara. Em geral, um teste deve esperar que micro-tasks terminem. Quando microtasks pendentes são esperados, chame flushMicrotasks para liberar a fila de micro-task e evitar o erro. |
ComponentFixtureAutoDetect |
Um token de provider para um service que ativa a detecção automática de mudanças. |
getTestBed |
Obtém a instância atual do TestBed. Geralmente desnecessário porque os métodos de classe estáticos da classe TestBed são tipicamente suficientes. A instância TestBed expõe alguns membros raramente usados que não estão disponíveis como métodos estáticos. |
Resumo da classe TestBed
A classe TestBed é uma das principais utilitários de teste do Angular.
Sua API é bastante grande e pode ser avassaladora até que você a explore, um pouco de cada vez.
Leia a parte inicial deste guia primeiro para obter o básico antes de tentar absorver a API completa.
A definição do módulo passada para configureTestingModule é um subconjunto das propriedades de metadados @NgModule.
type TestModuleMetadata = {providers?: any[];declarations?: any[];imports?: any[];schemas?: Array<SchemaMetadata | any[]>;};
Cada método override recebe um MetadataOverride<T> onde T é o tipo de metadados apropriado para o método, ou seja, o parâmetro de um @NgModule, @Component, @Directive ou @Pipe.
type MetadataOverride<T> = {add?: Partial<T>;remove?: Partial<T>;set?: Partial<T>;};
A API TestBed consiste de métodos de classe estáticos que atualizam ou referenciam uma instância global do TestBed.
Internamente, todos os métodos estáticos cobrem métodos da instância TestBed de runtime atual, que também é retornada pela função getTestBed().
Chame métodos TestBed dentro de um beforeEach() para garantir um novo começo antes de cada teste individual.
Aqui estão os métodos estáticos mais importantes, em ordem de utilidade provável.
| Métodos | Detalhes |
|---|---|
configureTestingModule |
Os shims de teste estabelecem o ambiente de teste inicial e um módulo de teste padrão. O módulo de teste padrão é configurado com declarações básicas e alguns substitutos de service do Angular que todo testador precisa. Chame configureTestingModule para refinar a configuração do módulo de teste para um conjunto particular de testes adicionando e removendo imports, declarations (de components, directives e pipes) e providers. |
compileComponents |
Compile o módulo de teste de forma assíncrona depois de ter terminado de configurá-lo. Você deve chamar este método se qualquer um dos components do módulo de teste tiver um templateUrl ou styleUrls porque buscar arquivos de template e estilo de component é necessariamente assíncrono. Veja compileComponents. Após chamar compileComponents, a configuração TestBed é congelada para a duração da spec atual. |
createComponent<T> |
Crie uma instância de um component do tipo T baseado na configuração TestBed atual. Após chamar createComponent, a configuração TestBed é congelada para a duração da spec atual. |
overrideModule |
Substitua metadados para o NgModule fornecido. Lembre-se que módulos podem importar outros módulos. O método overrideModule pode alcançar profundamente no módulo de teste atual para modificar um desses módulos internos. |
overrideComponent |
Substitua metadados para a classe de component fornecida, que poderia estar aninhada profundamente dentro de um módulo interno. |
overrideDirective |
Substitua metadados para a classe de directive fornecida, que poderia estar aninhada profundamente dentro de um módulo interno. |
overridePipe |
Substitua metadados para a classe de pipe fornecida, que poderia estar aninhada profundamente dentro de um módulo interno. |
|
inject | Recupere um service do injetor TestBed atual. A função inject é frequentemente adequada para este propósito. Mas inject lança um erro se não puder fornecer o service.
E se o service for opcional?
O método TestBed.inject() recebe um segundo parâmetro opcional, o objeto a retornar se o Angular não puder encontrar o provider (null neste exemplo): TestBed.inject, a configuração TestBed é congelada para a duração da spec atual. |
|
initTestEnvironment | Inicialize o ambiente de teste para toda a execução de teste.
Os shims de teste o chamam para você, então raramente há razão para você chamá-lo você mesmo.
Chame este método exatamente uma vez. Para mudar este padrão no meio de uma execução de teste, chame resetTestEnvironment primeiro.
Especifique a factory do compilador Angular, um PlatformRef e um módulo de teste Angular padrão. Alternativas para plataformas não-browser estão disponíveis na forma geral @angular/platform-<platform_name>/testing/<platform_name>. |
| resetTestEnvironment | Redefina o ambiente de teste inicial, incluindo o módulo de teste padrão. |
Alguns dos métodos de instância TestBed não são cobertos por métodos de classe estáticos TestBed.
Estes são raramente necessários.
O ComponentFixture
O TestBed.createComponent<T> cria uma instância do component T e retorna um ComponentFixture fortemente tipado para aquele component.
As propriedades e métodos ComponentFixture fornecem acesso ao component, sua representação DOM e aspectos de seu ambiente Angular.
Propriedades de ComponentFixture
Aqui estão as propriedades mais importantes para testadores, em ordem de utilidade provável.
| Propriedades | Detalhes |
|---|---|
componentInstance |
A instância da classe de component criada por TestBed.createComponent. |
debugElement |
O DebugElement associado com o elemento raiz do component. O debugElement fornece insight sobre o component e seu elemento DOM durante teste e debugging. É uma propriedade crítica para testadores. Os membros mais interessantes são cobertos abaixo. |
nativeElement |
O elemento DOM nativo na raiz do component. |
changeDetectorRef |
O ChangeDetectorRef para o component. O ChangeDetectorRef é mais valioso ao testar um component que tem o método ChangeDetectionStrategy.OnPush ou a detecção de mudanças do component está sob seu controle programático. |
Métodos de ComponentFixture
Os métodos fixture fazem o Angular realizar certas tarefas na árvore de components. Chame esses métodos para acionar comportamento Angular em resposta à ação simulada do usuário.
Aqui estão os métodos mais úteis para testadores.
| Métodos | Detalhes |
|---|---|
detectChanges |
Aciona um ciclo de detecção de mudanças para o component. Chame-o para inicializar o component (ele chama ngOnInit) e depois que seu código de teste alterar os valores de propriedade vinculados a dados do component. O Angular não pode ver que você mudou personComponent.name e não atualizará o binding name até que você chame detectChanges. Executa checkNoChanges depois para confirmar que não há atualizações circulares, a menos que chamado como detectChanges(false); |
autoDetectChanges |
Defina isso como true quando você quiser que o fixture detecte mudanças automaticamente. Quando autodetect é true, o fixture de teste chama detectChanges imediatamente após criar o component. Então ele escuta eventos de zone pertinentes e chama detectChanges de acordo. Quando seu código de teste modifica valores de propriedade do component diretamente, você provavelmente ainda terá que chamar fixture.detectChanges para acionar atualizações de data binding. O padrão é false. Testadores que preferem controle fino sobre comportamento de teste tendem a mantê-lo false. |
checkNoChanges |
Execute uma execução de detecção de mudanças para garantir que não haja mudanças pendentes. Lança exceções se houver. |
isStable |
Se o fixture está atualmente estável, retorna true. Se há tarefas assíncronas que não foram completadas, retorna false. |
whenStable |
Retorna uma promise que resolve quando o fixture está estável. Para retomar testes após conclusão de atividade assíncrona ou detecção de mudanças assíncrona, conecte essa promise. Veja whenStable. |
destroy |
Aciona a destruição do component. |
DebugElement
O DebugElement fornece insights cruciais sobre a representação DOM do component.
A partir do DebugElement do component raiz de teste retornado por fixture.debugElement, você pode caminhar (e consultar) toda a árvore de elementos e components do fixture.
Aqui estão os membros DebugElement mais úteis para testadores, em ordem aproximada de utilidade:
| Membros | Detalhes |
|---|---|
nativeElement |
O elemento DOM correspondente no browser |
query |
Chamar query(predicate: Predicate<DebugElement>) retorna o primeiro DebugElement que corresponde ao predicado em qualquer profundidade na subárvore. |
queryAll |
Chamar queryAll(predicate: Predicate<DebugElement>) retorna todos os DebugElements que correspondem ao predicado em qualquer profundidade na subárvore. |
injector |
O injetor de dependência do host. Por exemplo, o injetor da instância de component do elemento raiz. |
componentInstance |
A própria instância de component do elemento, se houver uma. |
context |
Um objeto que fornece contexto pai para este elemento. Frequentemente uma instância de component ancestral que governa este elemento. Quando um elemento é repetido dentro de um bloco @for, o context é um RepeaterContext cuja propriedade $implicit é o valor da instância de linha. Por exemplo, o hero em @for(hero of heroes; ...). |
children |
Os filhos DebugElement imediatos. Caminhe pela árvore descendo através de children. DebugElement também tem childNodes, uma lista de objetos DebugNode. DebugElement deriva de objetos DebugNode e frequentemente há mais nodes do que elementos. Testadores geralmente podem ignorar nodes simples. |
parent |
O pai DebugElement. Null se este for o elemento raiz. |
name |
O nome da tag do elemento, se for um elemento. |
triggerEventHandler |
Aciona o evento por seu nome se houver um listener correspondente na coleção listeners do elemento. O segundo parâmetro é o objeto de evento esperado pelo handler. Veja triggerEventHandler. Se o evento não tiver um listener ou houver algum outro problema, considere chamar nativeElement.dispatchEvent(eventObject). |
listeners |
Os callbacks anexados às propriedades @Output do component e/ou às propriedades de evento do elemento. |
providerTokens |
Os tokens de lookup do injetor deste component. Inclui o próprio component além dos tokens que o component lista em seus metadados providers. |
source |
Onde encontrar este elemento no template de component fonte. |
references |
Dicionário de objetos associados com variáveis locais de template (por exemplo, #foo), indexado pelo nome da variável local. |
Os métodos DebugElement.query(predicate) e DebugElement.queryAll(predicate) recebem um predicado que filtra a subárvore do elemento fonte para DebugElement correspondentes.
O predicado é qualquer método que recebe um DebugElement e retorna um valor truthy.
O exemplo a seguir encontra todos os DebugElements com uma referência a uma variável local de template chamada "content":
app/demo/demo.testbed.spec.ts
import {Component, DebugElement, Injectable} from '@angular/core';import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync,} from '@angular/core/testing';import {FormsModule, NgControl, NgModel} from '@angular/forms';import {By} from '@angular/platform-browser';import {addMatchers, click} from '../../testing';import { BankAccountComponent, BankAccountParentComponent, Child1Component, Child2Component, Child3Component, ExternalTemplateComponent, InputComponent, IoComponent, IoParentComponent, LightswitchComponent, MasterService, MyIfChildComponent, MyIfComponent, MyIfParentComponent, NeedsContentComponent, ParentComponent, ReversePipeComponent, ShellComponent, TestProvidersComponent, TestViewProvidersComponent, ValueService,} from './demo';export class NotProvided extends ValueService { /* example below */}beforeEach(addMatchers);describe('demo (with TestBed):', () => { //////// Service Tests ///////////// describe('ValueService', () => { let service: ValueService; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); service = TestBed.inject(ValueService); }); it('should use ValueService', () => { service = TestBed.inject(ValueService); expect(service.getValue()).toBe('real value'); }); it('can inject a default value when service is not provided', () => { expect(TestBed.inject(NotProvided, null)).toBeNull(); }); it('test should wait for ValueService.getPromiseValue', waitForAsync(() => { service.getPromiseValue().then((value) => expect(value).toBe('promise value')); })); it('test should wait for ValueService.getObservableValue', waitForAsync(() => { service.getObservableValue().subscribe((value) => expect(value).toBe('observable value')); })); // Must use done. See https://github.com/angular/angular/issues/10127 it('test should wait for ValueService.getObservableDelayValue', (done: DoneFn) => { service.getObservableDelayValue().subscribe((value) => { expect(value).toBe('observable delay value'); done(); }); }); it('should allow the use of fakeAsync', fakeAsync(() => { let value: any; service.getPromiseValue().then((val: any) => (value = val)); tick(); // Trigger JS engine cycle until all promises resolve. expect(value).toBe('promise value'); })); }); describe('MasterService', () => { let masterService: MasterService; let valueServiceSpy: jasmine.SpyObj<ValueService>; beforeEach(() => { const spy = jasmine.createSpyObj('ValueService', ['getValue']); TestBed.configureTestingModule({ // Provide both the service-to-test and its (spy) dependency providers: [MasterService, {provide: ValueService, useValue: spy}], }); // Inject both the service-to-test and its (spy) dependency masterService = TestBed.inject(MasterService); valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>; }); it('#getValue should return stubbed value from a spy', () => { const stubValue = 'stub value'; valueServiceSpy.getValue.and.returnValue(stubValue); expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue); expect(valueServiceSpy.getValue.calls.count()) .withContext('spy method was called once') .toBe(1); expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue); }); }); describe('use inject within `it`', () => { beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); it('should use modified providers', inject([ValueService], (service: ValueService) => { service.setValue('value modified in beforeEach'); expect(service.getValue()).toBe('value modified in beforeEach'); })); }); describe('using waitForAsync(inject) within beforeEach', () => { let serviceValue: string; beforeEach(() => { TestBed.configureTestingModule({providers: [ValueService]}); }); beforeEach(waitForAsync( inject([ValueService], (service: ValueService) => { service.getPromiseValue().then((value) => (serviceValue = value)); }), )); it('should use asynchronously modified value ... in synchronous test', () => { expect(serviceValue).toBe('promise value'); }); }); /////////// Component Tests ////////////////// describe('TestBed component tests', () => { // beforeEach(waitForAsync(() => { // TestBed.configureTestingModule() // // Compile everything in DemoModule // ; // })); it('should create a component with inline template', () => { const fixture = TestBed.createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Child'); }); it('should create a component with external template', () => { const fixture = TestBed.createComponent(ExternalTemplateComponent); fixture.detectChanges(); expect(fixture).toHaveText('from external template'); }); it('should allow changing members of the component', () => { const fixture = TestBed.createComponent(MyIfComponent); fixture.detectChanges(); expect(fixture).toHaveText('MyIf()'); fixture.componentInstance.showMore = true; fixture.detectChanges(); expect(fixture).toHaveText('MyIf(More)'); }); it('should create a nested component bound to inputs/outputs', () => { const fixture = TestBed.createComponent(IoParentComponent); fixture.detectChanges(); const heroes = fixture.debugElement.queryAll(By.css('.hero')); expect(heroes.length).withContext('has heroes').toBeGreaterThan(0); const comp = fixture.componentInstance; const hero = comp.heroes[0]; click(heroes[0]); fixture.detectChanges(); const selected = fixture.debugElement.query(By.css('p')); expect(selected).toHaveText(hero.name); }); it('can access the instance variable of an `*ngFor` row component', () => { const fixture = TestBed.createComponent(IoParentComponent); const comp = fixture.componentInstance; const heroName = comp.heroes[0].name; // first hero's name fixture.detectChanges(); const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow const hero = ngForRow.context.hero; // the hero object passed into the row expect(hero.name).withContext('ngRow.context.hero').toBe(heroName); const rowComp = ngForRow.componentInstance; // jasmine.any is an "instance-of-type" test. expect(rowComp).withContext('component is IoComp').toEqual(jasmine.any(IoComponent)); expect(rowComp.hero.name).withContext('component.hero').toBe(heroName); }); it('should support clicking a button', () => { const fixture = TestBed.createComponent(LightswitchComponent); const btn = fixture.debugElement.query(By.css('button')); const span = fixture.debugElement.query(By.css('span')).nativeElement; fixture.detectChanges(); expect(span.textContent) .withContext('before click') .toMatch(/is off/i); click(btn); fixture.detectChanges(); expect(span.textContent).withContext('after click').toMatch(/is on/i); }); // ngModel is async so we must wait for it with promise-based `whenStable` it('should support entering text in input box (ngModel)', waitForAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box fixture .whenStable() .then(() => { expect(input.value) .withContext( `After ngModel updates input box, input.value should be ${expectedOrigName} `, ) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name()) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); return fixture.whenStable(); }) .then(() => { expect(comp.name()) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); }); })); // fakeAsync version of ngModel input test enables sync test style // synchronous `tick` replaces asynchronous promise-base `whenStable` it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { const expectedOrigName = 'John'; const expectedNewName = 'Sally'; const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; expect(comp.name) .withContext(`At start name should be ${expectedOrigName} `) .toBe(expectedOrigName); // wait until ngModel binds comp.name to input box tick(); expect(input.value) .withContext(`After ngModel updates input box, input.value should be ${expectedOrigName} `) .toBe(expectedOrigName); // simulate user entering new name in input input.value = expectedNewName; // that change doesn't flow to the component immediately expect(comp.name) .withContext( `comp.name should still be ${expectedOrigName} after value change, before binding happens`, ) .toBe(expectedOrigName); // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.name input.dispatchEvent(new Event('input')); tick(); expect(comp.name) .withContext(`After ngModel updates the model, comp.name should be ${expectedNewName} `) .toBe(expectedNewName); })); it('ReversePipeComp should reverse the input text', fakeAsync(() => { const inputText = 'the quick brown fox.'; const expectedText = '.xof nworb kciuq eht'; const fixture = TestBed.createComponent(ReversePipeComponent); fixture.detectChanges(); const comp = fixture.componentInstance; const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; // simulate user entering new name in input input.value = inputText; // Dispatch a DOM event so that Angular learns of input value change. // then wait a tick while ngModel pushes input.box value to comp.text // and Angular updates the output span input.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(span.textContent).withContext('output span').toBe(expectedText); expect(comp.text).withContext('component.text').toBe(inputText); })); // Use this technique to find attached directives of any kind it('can examine attached directives and listeners', () => { const fixture = TestBed.createComponent(InputComponent); fixture.detectChanges(); const inputEl = fixture.debugElement.query(By.css('input')); expect(inputEl.providerTokens).withContext('NgModel directive').toContain(NgModel); const ngControl = inputEl.injector.get(NgControl); expect(ngControl).withContext('NgControl directive').toEqual(jasmine.any(NgControl)); expect(inputEl.listeners.length).withContext('several listeners attached').toBeGreaterThan(2); }); it('BankAccountComponent should set attributes, styles, classes, and properties', () => { const fixture = TestBed.createComponent(BankAccountParentComponent); fixture.detectChanges(); const comp = fixture.componentInstance; // the only child is debugElement of the BankAccount component const el = fixture.debugElement.children[0]; const childComp = el.componentInstance as BankAccountComponent; expect(childComp).toEqual(jasmine.any(BankAccountComponent)); expect(el.context).withContext('context is the child component').toBe(childComp); expect(el.attributes['account']).withContext('account attribute').toBe(childComp.id); expect(el.attributes['bank']).withContext('bank attribute').toBe(childComp.bank); expect(el.classes['closed']).withContext('closed class').toBe(true); expect(el.classes['open']).withContext('open class').toBeFalsy(); expect(el.styles['color']).withContext('color style').toBe(comp.color); expect(el.styles['width']) .withContext('width style') .toBe(comp.width + 'px'); // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? // expect(el.properties['customProperty']).toBe(true, 'customProperty'); }); }); describe('TestBed component overrides:', () => { it("should override ChildComp's template", () => { const fixture = TestBed.configureTestingModule({ imports: [Child1Component], }) .overrideComponent(Child1Component, { set: {template: '<span>Fake</span>'}, }) .createComponent(Child1Component); fixture.detectChanges(); expect(fixture).toHaveText('Fake'); }); it("should override TestProvidersComp's ValueService provider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestProvidersComponent], }) .overrideComponent(TestProvidersComponent, { remove: {providers: [ValueService]}, add: {providers: [{provide: ValueService, useClass: FakeValueService}]}, // Or replace them all (this component has only one provider) // set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }, }) .createComponent(TestProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value', 'text'); // Explore the providerTokens const tokens = fixture.debugElement.providerTokens; expect(tokens).withContext('component ctor').toContain(fixture.componentInstance.constructor); expect(tokens).withContext('TestProvidersComp').toContain(TestProvidersComponent); expect(tokens).withContext('ValueService').toContain(ValueService); }); it("should override TestViewProvidersComp's ValueService viewProvider", () => { const fixture = TestBed.configureTestingModule({ imports: [TestViewProvidersComponent], }) .overrideComponent(TestViewProvidersComponent, { // remove: { viewProviders: [ValueService]}, // add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] // }, // Or replace them all (this component has only one viewProvider) set: {viewProviders: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestViewProvidersComponent); fixture.detectChanges(); expect(fixture).toHaveText('injected value: faked value'); }); it("injected provider should not be same as component's provider", () => { // TestComponent is parent of TestProvidersComponent @Component({ template: '<my-service-comp></my-service-comp>', imports: [TestProvidersComponent], }) class TestComponent {} // 3 levels of ValueService provider: module, TestComponent, TestProvidersComponent const fixture = TestBed.configureTestingModule({ imports: [TestComponent, TestProvidersComponent], providers: [ValueService], }) .overrideComponent(TestComponent, { set: {providers: [{provide: ValueService, useValue: {}}]}, }) .overrideComponent(TestProvidersComponent, { set: {providers: [{provide: ValueService, useClass: FakeValueService}]}, }) .createComponent(TestComponent); let testBedProvider!: ValueService; // `inject` uses TestBed's injector inject([ValueService], (s: ValueService) => (testBedProvider = s))(); const tcProvider = fixture.debugElement.injector.get(ValueService) as ValueService; const tpcProvider = fixture.debugElement.children[0].injector.get( ValueService, ) as FakeValueService; expect(testBedProvider).withContext('testBed/tc not same providers').not.toBe(tcProvider); expect(testBedProvider).withContext('testBed/tpc not same providers').not.toBe(tpcProvider); expect(testBedProvider instanceof ValueService) .withContext('testBedProvider is ValueService') .toBe(true); expect(tcProvider) .withContext('tcProvider is {}') .toEqual({} as ValueService); expect(tpcProvider instanceof FakeValueService) .withContext('tpcProvider is FakeValueService') .toBe(true); }); it('can access template local variables as references', () => { const fixture = TestBed.configureTestingModule({ imports: [ ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component, ], }) .overrideComponent(ShellComponent, { set: { selector: 'test-shell', imports: [NeedsContentComponent, Child1Component, Child2Component, Child3Component], template: ` <needs-content #nc> <child-1 #content text="My"></child-1> <child-2 #content text="dog"></child-2> <child-2 text="has"></child-2> <child-3 #content text="fleas"></child-3> <div #content>!</div> </needs-content> `, }, }) .createComponent(ShellComponent); fixture.detectChanges(); // NeedsContentComp is the child of ShellComp const el = fixture.debugElement.children[0]; const comp = el.componentInstance; expect(comp.children.toArray().length) .withContext('three different child components and an ElementRef with #content') .toBe(4); expect(el.references['nc']).withContext('#nc reference to component').toBe(comp); // Filter for DebugElements with a #content reference const contentRefs = el.queryAll((de) => de.references['content']); expect(contentRefs.length).withContext('elements w/ a #content reference').toBe(4); }); }); describe('nested (one-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildComponent]}, }); }); it('ParentComp should use Fake Child component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child)'); }); }); describe('nested (two-deep) component override', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent], }).overrideComponent(ParentComponent, { set: {imports: [FakeChildWithGrandchildComponent, FakeGrandchildComponent]}, }); }); it('should use Fake Grandchild component', () => { const fixture = TestBed.createComponent(ParentComponent); fixture.detectChanges(); expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); }); });});////////// Fakes ///////////@Component({ selector: 'child-1', template: 'Fake Child',})class FakeChildComponent {}@Component({ selector: 'grandchild-1', template: 'Fake Grandchild',})class FakeGrandchildComponent {}@Component({ selector: 'child-1', imports: [FakeGrandchildComponent], template: 'Fake Child(<grandchild-1></grandchild-1>)',})class FakeChildWithGrandchildComponent {}@Injectable()class FakeValueService extends ValueService { override value = 'faked value';}
A classe By do Angular tem três métodos estáticos para predicados comuns:
| Método estático | Detalhes |
|---|---|
By.all |
Retorna todos os elementos |
By.css(selector) |
Retorna elementos com seletores CSS correspondentes |
By.directive(directive) |
Retorna elementos que o Angular correspondeu a uma instância da classe de directive |
app/hero/hero-list.component.spec.ts
import {ComponentFixture, fakeAsync, TestBed, tick, waitForAsync} from '@angular/core/testing';import {By} from '@angular/platform-browser';import {DebugElement} from '@angular/core';import {Router} from '@angular/router';import {addMatchers} from '../../testing';import {HeroService} from '../model/hero.service';import {getTestHeroes, TestHeroService} from '../model/testing/test-hero.service';import {HeroListComponent} from './hero-list.component';import {HighlightDirective} from '../shared/highlight.directive';import {appConfig} from '../app.config';const HEROES = getTestHeroes();let comp: HeroListComponent;let fixture: ComponentFixture<HeroListComponent>;let page: Page;/////// Tests //////describe('HeroListComponent', () => { beforeEach(waitForAsync(() => { addMatchers(); const routerSpy = jasmine.createSpyObj('Router', ['navigate']); TestBed.configureTestingModule( Object.assign({}, appConfig, { providers: [ {provide: HeroService, useClass: TestHeroService}, {provide: Router, useValue: routerSpy}, ], }), ) .then(createComponent); })); it('should display heroes', () => { expect(page.heroRows.length).toBeGreaterThan(0); }); it('1st hero should match 1st test hero', () => { const expectedHero = HEROES[0]; const actualHero = page.heroRows[0].textContent; expect(actualHero).withContext('hero.id').toContain(expectedHero.id.toString()); expect(actualHero).withContext('hero.name').toContain(expectedHero.name); }); it('should select hero on click', fakeAsync(() => { const expectedHero = HEROES[1]; const btn = page.heroRows[1].querySelector('button'); btn!.dispatchEvent(new Event('click')); tick(); // `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService expect(comp.selectedHero).toEqual(expectedHero); })); it('should navigate to selected hero detail on click', fakeAsync(() => { const expectedHero = HEROES[1]; const btn = page.heroRows[1].querySelector('button'); btn!.dispatchEvent(new Event('click')); tick(); // should have navigated expect(page.navSpy.calls.any()).withContext('navigate called').toBe(true); // composed hero detail will be URL like 'heroes/42' // expect link array with the route path and hero id // first argument to router.navigate is link array const navArgs = page.navSpy.calls.first().args[0]; expect(navArgs[0]).withContext('nav to heroes detail URL').toContain('heroes'); expect(navArgs[1]).withContext('expected hero.id').toBe(expectedHero.id); })); it('should find `HighlightDirective` with `By.directive', () => { // Can find DebugElement either by css selector or by directive const h2 = fixture.debugElement.query(By.css('h2')); const directive = fixture.debugElement.query(By.directive(HighlightDirective)); expect(h2).toBe(directive); }); it('should color header with `HighlightDirective`', () => { const h2 = page.highlightDe.nativeElement as HTMLElement; const bgColor = h2.style.backgroundColor; // different browsers report color values differently const isExpectedColor = bgColor === 'gold' || bgColor === 'rgb(255, 215, 0)'; expect(isExpectedColor).withContext('backgroundColor').toBe(true); }); it("the `HighlightDirective` is among the element's providers", () => { expect(page.highlightDe.providerTokens) .withContext('HighlightDirective') .toContain(HighlightDirective); });});/////////// Helpers //////** Create the component and set the `page` test variables */function createComponent() { fixture = TestBed.createComponent(HeroListComponent); comp = fixture.componentInstance; // change detection triggers ngOnInit which gets a hero fixture.detectChanges(); return fixture.whenStable().then(() => { // got the heroes and updated component // change detection updates the view fixture.detectChanges(); page = new Page(); });}class Page { /** Hero line elements */ heroRows: HTMLLIElement[]; /** Highlighted DebugElement */ highlightDe: DebugElement; /** Spy on router navigate method */ navSpy: jasmine.Spy; constructor() { const heroRowNodes = fixture.nativeElement.querySelectorAll('li'); this.heroRows = Array.from(heroRowNodes); // Find the first element with an attached HighlightDirective this.highlightDe = fixture.debugElement.query(By.directive(HighlightDirective)); // Get the component's injected router navigation spy const routerSpy = fixture.debugElement.injector.get(Router); this.navSpy = routerSpy.navigate as jasmine.Spy; }}