Testowanie frontendu - Cz. 3. Testowanie komponentów angularowych z inputami i outputami
Czas na kolejną dawkę informacji dotyczących testowania przy użyciu Jasmine. Po przeczytaniu wcześniejszych wpisów (Cz. 1 i Cz. 2) pora skupić się na testowaniu komponentów angularowych, a w szczególności ich wejść i wyjść. Przykłady oprzemy o aplikację, która będzie składała się z kilku drobnych wzajemnie się ze sobą komunikujących elementów.
Schemat przykładowej aplikacji wygląda następująco:
Jest to prosty program mający na celu dodawanie i zapisywanie wyników. Został rozpisany na komponenty tak, aby można było spokojnie przetestować wejścia i wyjścia w różnych wariantach.
Aplikacja zawiera dwa komponenty przekazujące wpisaną liczbę (CalculatorInputFieldComponent
), następnie wysyłane są one do komponentu, który zliczy nam wynik (CalculatorResultComponent
) i zaprezentuje go w czytelnej formie (CalucatorResultPresentationComponent
). Z poziomu komponentu prezentującego wyniki możemy je również zapisać (CalculatorSavedListComponent
). Strzałki na diagramie oznaczają wejścia i wyjścia komponentów.
Pisanie testów dotyczących @Input
Na początek weźmy na warsztat komponent pozwalający wyświetlić listę wyników, które chcielibyśmy zapisać.
@Component({
selector: 'calculator-saved-list',
template: `
<div class="result">{{getText()}}{{savedValues}}</div>
`,
})
export class CalculatorSavedListComponent {
@Input() savedValues: Array<number>;
getText(): string {
if (this.savedValues.length === 0) {
return 'Brak zapisanych wartości';
} else {
return 'Zapisane wartości to: ';
}
}
}
Trudno sobie wyobrazić prostszy komponent. Mamy tutaj jeden input, przekazujący listę wyników, którą chcemy wyświetlić.
Pora na napisanie testu sprawdzającego, czy komponent poprawnie interpretuje przekazane mu wyniki
describe('CalculatorSavedListComponent', () => {
let component: CalculatorSavedListComponent;
let fixture: ComponentFixture<CalculatorSavedListComponent>;
let resultText: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CalculatorSavedListComponent]
}).compileComponents();
fixture = TestBed.createComponent(CalculatorSavedListComponent);
component = fixture.componentInstance;
resultText = fixture.debugElement.query(By.css('.result'));
}));
it('should show correct text when list is empty', () => {
component.savedValues = [];
fixture.detectChanges();
expect(resultText.childNodes[0].nativeNode.wholeText).toEqual('Brak zapisanych wartości');
});
it('should show elements of list with correct text', () => {
component.savedValues = [12, 3];
fixture.detectChanges();
expect(resultText.childNodes[0].nativeNode.wholeText).toEqual('Zapisane wartości to: 12,3');
});
});
Zaczęliśmy tutaj, podobnie jak w poprzednim artykule, od stworzenia TestBed
i dodatkowo wyciągnęliśmy za pomocą zapytania element html prezentujący tekst umieszczony z poziomu komponentu. Możemy się do niego odwołać na wiele sposobów, na przykład za pomocą klasy lub id.
Dysponujemy tutaj dwoma testami o identycznej strukturze. Przekazujemy wartości w inpucie i sprawdzamy, czy element umieszczony w html został poprawnie zmieniony. Jeżeli inputów w komponencie mamy więcej, możemy je przetestować dokładnie w ten sam sposób.
Pisanie testów dotyczących @Output
Następnie napiszmy test do komponentu pozwalającego wpisać liczbę, którą przekazuje do komponentu nadrzędnego.
@Component({
selector: 'calculator-input-field',
template: `
<input (keyup)="onKey($event)">
`,
})
export class CalculatorInputFieldComponent {
@Output() fieldValue = new EventEmitter<number>();
onKey(event) {
this.fieldValue.emit(event.target.value);
}
}
Komponent równie prosty jak poprzedni z takim wyjątkiem, że mamy tutaj do czynienia z outputem.
describe('CalculatorInputFieldComponent', () => {
let component: CalculatorInputFieldComponent;
let fixture: ComponentFixture<CalculatorInputFieldComponent>;
let input: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CalculatorInputFieldComponent]
}).compileComponents();
fixture = TestBed.createComponent(CalculatorInputFieldComponent);
component = fixture.componentInstance;
input = fixture.debugElement.query(By.css('input'));
}));
it('should emit correct value on input change', () => {
component.fieldValue.subscribe((value) => expect(value).toBe('7'));
input.nativeElement.value = 7;
document.querySelector('input').dispatchEvent(new KeyboardEvent('keyup'));
});
});
Czyli standardowo, zaczynamy od przygotowania modułu testowego i uchwycenia elementu, który chcemy przetestować. Warto tutaj zaznaczyć, że istnieją inne sposoby na zainicjalizowanie komponentu niż TestBed
, wszystko zależy od tego co, i w jaki sposób, będziemy testować. Na przykład kiedy nie potrzebujemy testować elementów z DOM możemy zostać przy zwykłym inicjalizowaniu komponentów. Więcej szczegółów można znaleźć w dokumentacji Angulara.
Test zaczynamy od nasłuchiwania na zmienną fieldValue
po której spodziewamy się, że przekaże nam wartość wpisaną w pole. Następnie uzupełniamy wartość i aktywujemy event, pozwalający wywołać metodę nasłuchującą na zmiany w polu. Test zakończy się, kiedy zostanie wywołana metoda expect
sprawdzająca, czy wpisana przez nas wartość zgadza się z oczekiwaniami. Test może się również zakończyć niepowodzeniem, kiedy po upływie danego czasu (domyślna konfiguracja wskazuje na 5s) żaden expect
nie zostanie wywołany.
Na koniec możemy zebrać wiedzę i przetestować komponent zawierający wejścia, jak i wyjścia. Poniższy kod przedstawia komponent, który dodaje wskazane liczby oraz opcjonalnie potrafi wysłać dane o wyniku do komponentu ‘wyżej’.
@Component({
selector: 'calculator-result',
template: `
<calculator-result-presentation [calculationResult]="getCalculationResult()"></calculator-result-presentation>
<button (click)="saveValue()">Zapisz wartość</button>
`,
})
export class CalculatorResultComponent {
@Input() values: Array<number>;
@Input() text: string;
@Output() savedValue = new EventEmitter<number>();
getResult(): number {
return Number(this.values[0]) + Number(this.values[1]);
}
getCalculationResult(): string {
return `${this.text} ${this.getResult()}`;
}
saveValue() {
this.savedValue.emit(this.getResult());
}
}
Możemy zauważyć, że komponent potrzebuje innego komponentu (calculator-result-presentation
), któremu przekazuje wynik. W testach nie będzie miało to dużego znaczenia, należy jednak pamiętać o jego deklaracji przy tworzeniu TestBed
.
describe('CalculatorResultComponent', () => {
let component: CalculatorResultComponent;
let fixture: ComponentFixture<CalculatorResultComponent>;
let button: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
CalculatorResultComponent,
CalculatorResultPresentationComponent
]
}).compileComponents();
fixture = TestBed.createComponent(CalculatorResultComponent);
component = fixture.componentInstance;
button = fixture.debugElement.query(By.css('button'));
}));
it('should emit correct value on button', () => {
component.values = [1, 2];
component.savedValue.subscribe((value) => expect(value).toBe(3));
button.nativeElement.click();
});
});
Powyższy test przekazuje wartości do inputów oraz naciska przycisk. Jeżeli wszystko działa poprawnie, po jego naciśnięciu powinniśmy zostać powiadomieni o wyniku działaniu, który został wyemitowany na output.
Testowanie komponentów angularowych - podsumowanie.
Powyższe przykłady przedstawiają najprostsze scenariusze testowe. Jest to jednak dobry punkt wyjścia do napisania własnych testów komponentów angularowych, przy których zaprezentowane rozwiązania mogą okazać się użyteczne.
-
SENIOR FULLSTACK DEVELOPER (JAVA + ANGULAR) Poznań (hybrydowo) lub zdalnie UoP 14 900 - 20 590 PLN brutto
B2B 19 680 - 27 220 PLN netto -
REGULAR FULLSTACK DEVELOPER (JAVA + ANGULAR) Poznań (hybrydowo) lub zdalnie UoP 11 300 - 15 900 PLN brutto
B2B 14 950 - 21 000 PLN netto -
ZOBACZ WSZYSTKIE OGŁOSZENIA
newsletter
techniczny
Podobne wpisy
Czy wiesz, że w Angular 17 została wprowadzona alternatywa dla *ngIf?
-
SENIOR FULLSTACK DEVELOPER (JAVA + ANGULAR) Poznań (hybrydowo) lub zdalnie UoP 14 900 - 20 590 PLN brutto
B2B 19 680 - 27 220 PLN netto -
REGULAR FULLSTACK DEVELOPER (JAVA + ANGULAR) Poznań (hybrydowo) lub zdalnie UoP 11 300 - 15 900 PLN brutto
B2B 14 950 - 21 000 PLN netto -
ZOBACZ WSZYSTKIE OGŁOSZENIA