Chociaż najwyżej ocenione odpowiedzi działają, nie są one przykładem dobrych praktyk testowania, więc pomyślałem, że poszerzę odpowiedź Güntera o kilka praktycznych przykładów.
Wyobraźmy sobie, że mamy następujący prosty element:
@Component({
selector: 'my-demo',
template: `
<button (click)="buttonClicked()">Click Me!</button>
`
})
export class DemoComponent {
@Output() clicked = new EventEmitter<string>();
constructor() { }
buttonClicked(): void {
this.clicked.emit('clicked!');
}
}
Składnik jest testowanym systemem, szpiegowanie jego części powoduje przerwanie hermetyzacji. Testy komponentów kątowych powinny wiedzieć tylko o trzech rzeczach:
- DOM (dostępny przez np
fixture.nativeElement.querySelector
);
- Nazwy
@Input
s i@Output
S; i
- Usługi współpracy (wstrzykiwane za pośrednictwem systemu DI).
Wszystko, co wiąże się z bezpośrednim wywoływaniem metod w instancji lub szpiegowaniem części komponentu, jest zbyt ściśle powiązane z implementacją i może powodować tarcie w refaktoryzacji - test podwójny powinien być używany tylko dla współpracowników. W tym przypadku, ponieważ nie mamy współpracowników, nie powinniśmy potrzebować żadnych fałszywych szpiegów ani innych podwójnych testów.
Jednym ze sposobów sprawdzenia tego jest zapisanie się bezpośrednio do emitera, a następnie wywołanie akcji kliknięcia (zobacz Komponent z wejściami i wyjściami ):
describe('DemoComponent', () => {
let component: DemoComponent;
let fixture: ComponentFixture<DemoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DemoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DemoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should emit when clicked', () => {
let emitted: string;
component.clicked.subscribe((event: string) => {
emitted = event;
});
fixture.nativeElement.querySelector('button').click();
expect(emitted).toBe('clicked!');
});
});
Chociaż to współdziała bezpośrednio z wystąpieniem składnika, nazwa @Output
jest częścią publicznego interfejsu API, więc nie jest zbyt ściśle powiązana.
Alternatywnie możesz utworzyć prostego hosta testowego (zobacz Komponent wewnątrz hosta testowego ) i faktycznie zamontować swój komponent:
@Component({
selector: 'test-host',
template: `
<my-demo (clicked)="onClicked($event)"></my-demo>
`
})
class TestHostComponent {
lastClick = '';
onClicked(value: string): void {
this.lastClick = value;
}
}
następnie przetestuj komponent w kontekście:
describe('DemoComponent', () => {
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestHostComponent, DemoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should emit when clicked', () => {
fixture.nativeElement.querySelector('button').click();
expect(component.lastClick).toBe('clicked!');
});
});
componentInstance
Tu jest gospodarzem testu , więc możemy być pewni, że nie jesteśmy zbyt połączeniu ze składnikiem jesteśmy faktycznie testowania.