Angular - Viết unit test với Mock và Spy
Trước khi đào sâu vào chủ đề của bài viết, tôi sẽ giới thiệu qua về việc viết unit test trong Angular. Angular sử dụng Jasmine và Karma để viết và chạy test. Jasmine là một javascript testing framework hỗ trợ BDD (Behavior Driven Development), nó cố gắng mô tả các tests trong một định dạng ...
Trước khi đào sâu vào chủ đề của bài viết, tôi sẽ giới thiệu qua về việc viết unit test trong Angular. Angular sử dụng Jasmine và Karma để viết và chạy test.
- Jasmine là một javascript testing framework hỗ trợ BDD (Behavior Driven Development), nó cố gắng mô tả các tests trong một định dạng chúng ta có thể dễ dàng đọc hiểu, ngay cả đối với những người không am hiểu kĩ thuật cũng có thể hiểu những gì đang test ở đây.
- Karma là một test runner, nó sinh ra browser và chạy các jasmine tests bên trong nó từ command line. Chúng ta có thể theo dõi kết quả ở browser được sinh ra hoặc ở command line. Karma có cơ chế theo dõi sự thay đổi của codes để tự động chạy lại test.
Mục tiêu của bài viết
- Biết cách Mock với một fake class
- Biết cách Mock bằng cách extend class và override hàm
- Biết cách Mock sử dụng Spy
Sample Code
Ví dụ tôi có một LoginComponent, inject vào đó là AuthenticationService. Để đơn giản hóa việc test, ở đây tôi chỉ sử dụng AuthenticationService để kiểm tra người dùng đã được xác thực hay chưa, nếu chưa, LoginComponent sẽ hiển thị một nút Login để bắt người dùng phải đăng nhập trước khi muốn sử dụng trang web của tôi.
LoginComponent:
# login.component.ts import { Component } from '@angular/core'; import { AuthenticationService } from './authentication.service'; @Component({ selector: 'app-login', template: `<a [hidden]="isLoggedIn()">Login</a>` }) export class LoginComponent { constructor(private authService: AuthenticationService) { } isLoggedIn() { return this.authService.isAuthenticated(); } }
AuthenticationService:
# authentication.service.ts import { Injectable } from '@angular/core'; @Injectable() export class AuthUserService { isAuthenticated(): boolean { return !!localStorage.getItem('token'); } }
Trên thực tế, sẽ không có một trang web nào chỉ kiểm tra sự xuất hiện của key token trong localStorage để kết luận được rằng người dùng này đã được xác thực hay chưa, nhưng vì mục đích đơn giản nên là chúng ta sẽ kiểm tra như vậy đi ^^.
Ở những phần tiếp theo, tôi sẽ đi vào giới thiệu một số cách để có thể test được LoginComponent kia nha.
Viết test với một AuthenticationService thực
Chúng ta có thể test LoginComponent bằng cách sử dụng một instance thực của AuthenticationService. Với cách này, chúng ta sẽ cần phải cài đặt một số dữ liệu lưu trong localStorage.
# login.component.spec.ts import { LoginComponent } from './login.component'; import { AuthenticationService } from "./authentication.service"; describe('LoginComponent', () => { let component: LoginComponent; let service: AuthenticationService; beforeEach(() => { service = new AuthenticationService(); component = new LoginComponent(service); }); afterEach(() => { localStorage.removeItem('token'); }); it('isLoggedIn returns false when the user is not authenticated', () => { expect(component.isLoggedIn()).toBeFalsy(); }); it('isLoggedIn returns true when the user is authenticated', () => { localStorage.setItem('token', 'dG9rZW4='); expect(component.isLoggedIn()).toBeTruthy(); }); });
Cách này có vẻ không được ổn cho lắm, khi mà chúng ta cần phải biết rõ bên trong AuthenticationService hoạt động như thế nào. Thử tưởng tượng, nếu như LoginComponent inject thêm một vài service khác nữa, chúng ta cũng sẽ cần phải biết rõ cơ chế hoạt động của từng service đó.
Ngoài ra, ví dụ như AuthenticationService thay đổi cơ chế lưu trữ token, chẳng hạn lưu trữ trong sessionStorage hay cookies thay vì localStorage, khi đó test cho LoginComponent dĩ nhiên sẽ bị fail ở case isLoggedIn returns true when the user is authenticated bởi vì token đang được lưu ở trong localStorage mà.
Đây là lý do tại sao chúng ta nên viết test cho các class một cách cô lập, chúng ta chỉ cần quan tâm đến LoginComponent, không cần phải bận tâm đến những class phụ thuộc của nó khi viết test.
Chúng ta sẽ làm được điều đó bằng cách Mock các class phụ thuộc của LoginComponent. Mock là một hành động tạo ra một cái gì đó trông có vẻ giống với những class phụ thuộc, nhưng chúng ta có thể điều khiển kết quả của chúng trong test một cách dễ dàng. Sau đây là một số cách để tạo ra các mock
Mock với fake class
Trong cách này, chúng ta tạo một fake class của AuthenticationService có tên là MockAuthService. Trong MockAuthService chúng ta cũng tạo ra hàm isAuthenticated() giống như trong AuthenticationService, nhưng sẽ có thể điều khiển kết quả trả về của hàm đó dễ dàng hơn, mà không cần biết rõ xử lý thực sự bên trong của nó ra sao. Hãy xem đoạn codes ở dưới để rõ hơn nhé