Testowanie frontendu - Cz. 4 Testy jednostkowe kodu działającego asynchronicznie
Kończąc serię dotyczącą testowania komponentów Angularowych przy pomocy Jasmine, chciałbym poruszyć temat testów kodu wykonywanego asynchronicznie.
Testy jednostkowe asynchronicznych aplikacji frontendowych często wydają się być zagadką dla developerów. Na szczęście twórcy narzędzi pomyśleli również o tym i dostarczyli nam narzędzia, które zdecydowanie ułatwiają pracę z testowaniem takiego kodu.
W tym wpisie jednak nie poruszę kwestii testowania opartego na mockowaniu/stubowaniu kodu. Jeśli jesteś zainteresowany tym tematem, zachęcam do zajrzenia do artykułu Krzysztofa Czechowskiego o testowaniu serwisów.
Kod poddany testom
W celu sprawdzenia możliwości testowania asynchronicznych wywołań weźmy na warsztat przykładowy komponent:
@Component({
selector: 'app-company',
template: '<div *ngIf="messageVisible" id="welcomeMessage">Hello!</div>',
})
export class CompanyComponent {
messageVisible: boolean = false;
getCompany(): Promise<string> {
return Promise.resolve("company");
}
showMessage() {
setTimeout(() => {
this.messageVisible = true;
}, 2000)
}
}
Posiada on dwie metody: jedną, która zwraca Promise z nazwą firmy oraz drugą, która wykonuje zmianę widoczności flagi po dwóch sekundach. Na podstawie tej flagi wyświetlana jest wiadomość w templacie HTML.
Klasa testowa, do której będą dodawane test-case’y. W tym przypadku jest ona w 100% standardowa:
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let debugElement: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
debugElement = fixture.debugElement;
}));
});
Testowanie metod zwracających Promisy
Jeśli chcemy przetestować metodę, która zwraca wartość opakowaną w Promise, oraz której wynik nie jest zależny od dostępności zewnętrznych usług, możemy w łatwy sposób sprawdzić zwracane przez nie wartości przy pomocy mechanizmu async/await:
it('resolves company using async/await', async function () {
const company = await fixture.componentInstance.getCompany();
expect(company).toEqual("company");
});
Jeśli z jakiegoś powodu nie możesz wykorzystać async/await, to wówczas zastosowanie znajdzie tradycyjne rozwiązywanie Promisów:
it('resolves company promise manually', function () {
fixture.componentInstance.getCompany().then(company => {
expect(company).toEqual("company");
});
});
Oczekiwanie na wykonanie metody przy użyciu fakeAsync
Metoda showMessage()
z naszego komponentu ma narzucony czas dwóch sekund oczekiwania przed jej wykonaniem.
W teście możemy powtórzyć ten zabieg i po wywołaniu metody uruchomić asercje wewnątrz setTimeout()
. Jednak wprowadzanie realnego czasu oczekiwania nie jest efektywnym rozwiązaniem i bardzo spowolni nasze testy. Dzięki Angularowemu fakeAsync
możemy testować kod asynchroniczny, w synchroniczny sposób.
Zobaczmy:
it("tests the message visibility", fakeAsync(() => {
fixture.componentInstance.showMessage();
tick(2000);
fixture.detectChanges();
fixture.whenStable().then(() => {
const helloMessage = fixture.debugElement.query(By.css("#welcomeMessage"));
expect(helloMessage).toBeTruthy();
expect(helloMessage.nativeElement.innerHTML).toBe('Hello!');
})
}));
Spoglądając od góry:
- najpierw opakowujemy nasz test jednostkowy w blok fakeAsync(), który pozwala nam oszukać asynchroniczny przepływ,
- wywołujemy asynchroniczną metodę,
- symulujemy upływ czasu - w rzeczywistości nie trwa to dwóch sekund, jednak aplikacja “myśli”, że tyle upłynęło,
- wykrywamy zmiany, a kiedy detekcja zmian się zakończy, robimy tradycyjne asercje.
A co jeśli nie znamy czasu, który powinien upłynąć zanim wykonamy asercje? W miejscu tick(2000)
, możemy wykorzystać flush()
i efekt będzie dokładnie taki sam. Czym się charakteryzuje flush()
? Podobnie jak tick, symuluje on upływ czasu, jednak robi do momentu opustoszenia kolejki macrotasków (czyli m.in. setTimeout, setInterval).
Testowanie kodu asynchronicznego - podsumowanie
Dzięki uzbrojeniu JavaScriptu w wygodne mechanizmy oraz ułatwieniom ze strony Angulara, testowanie jednostkowe kodu działającego asynchronicznie staje się nawet nie tyle proste, co całkiem przyjemne.
-
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