Angular Uygulamalarında HttpClient İsteklerini Test Etmek
Angular uygulamaları genellikle arka uç servislerle iletişim kurmak için HttpClient modülünü kullanır. Bu iletişim, bir uygulamanın temel işlevselliğinin önemli bir parçası olduğundan, HTTP isteklerinin doğru bir şekilde yapıldığından ve beklenen yanıtları işlediğinden emin olmak için kapsamlı bir şekilde test edilmesi kritik öneme sahiptir. Bu makale, Angular’da HttpClient isteklerini test etmenin çeşitli yönlerini, kullanılan araçları ve en iyi uygulamaları derinlemesine inceleyecektir.
Temel Kavramlar ve Araçlar
Angular test stratejisi, genellikle birim testleri ve entegrasyon testlerinin bir kombinasyonunu içerir. HttpClient isteklerini test ederken, bu iki yaklaşım da önemlidir.
Unit Test Nedir? Neden Önemlidir?
Birim testleri, bir yazılım parçasının (örneğin, bir fonksiyon, metot veya sınıf) en küçük, bağımsız birimini izole ederek test etme işlemidir. Amaç, her birimin beklenen şekilde çalıştığını doğrulamaktır. HttpClient bağlamında, bir servisin HttpClient metotlarını doğru bir şekilde çağırıp çağırmadığını, doğru URL’leri ve parametreleri kullanıp kullanmadığını ve yanıtları düzgün bir şekilde işleyip işlemediğini test etmek birim test kapsamına girer. Bu testler hızlı çalışır ve sorunların kaynağını kolayca tespit etmeye yardımcı olur.
Entegrasyon Testi Nedir? Neden Önemlidir?
Entegrasyon testleri, farklı yazılım birimlerinin bir araya geldiğinde nasıl etkileşim kurduğunu test eder. HttpClient için, bir servisin bir komponent ile nasıl etkileşimde bulunduğunu, servisin HTTP isteğini yapıp yapmadığını ve komponentin bu isteğin sonucuna (başarı veya hata) nasıl tepki verdiğini test etmek entegrasyon testlerine örnektir. Entegrasyon testleri, sistemin daha büyük parçalarının birlikte sorunsuz çalıştığından emin olmak için önemlidir, ancak birim testlerinden daha yavaş olabilirler.
Angular Test Ortamı
Angular, testler için güçlü bir ekosistem sunar. Varsayılan olarak Jasmine test çatısı ve Karma test çalıştırıcısı kullanılır. TestBed, Angular’ın test modüllerini yapılandırmak ve test edilecek bileşenleri, servisleri veya direktifleri oluşturmak için kullandığı ana araçtır. HttpClient isteklerini test ederken, TestBed‘i HttpClientTestingModule ile birlikte kullanarak HTTP isteklerini taklit eden (mock) bir ortam oluştururuz.
HttpClientTestingModule ve HttpTestingController
Bunlar, Angular’da HTTP isteklerini test etmenin kalbinde yer alan iki ana araçtır:
* HttpClientTestingModule: Bu modül, HttpClient‘ın gerçek HTTP istekleri yapmak yerine taklit edilmiş bir versiyonunu sağlar. Bu sayede, testler sırasında ağ istekleri dışarıya çıkmaz ve testler daha hızlı ve güvenilir bir şekilde çalışır. Gerçek bir arka uç servisine bağımlılık ortadan kalkar.
* HttpTestingController: HttpClientTestingModule ile birlikte gelir ve taklit edilen HTTP isteklerini kontrol etmemizi sağlar. Yapılan HTTP isteklerini “yakalamak”, onlara özel yanıtlar (başarılı veri veya hata) göndermek ve bekleyen isteklerin kalmadığından emin olmak için kullanılır. Bu, test senaryolarımızı tamamen kontrol etmemizi sağlar.
Bir Servisi Test Etmek: Temel Yaklaşım
HttpClient isteklerini test etmenin en yaygın senaryosu, bir veri servisini test etmektir. Bu servisler genellikle GET, POST, PUT ve DELETE gibi HTTP metotlarını kullanarak arka uç ile iletişim kurar.
Senaryo Tanımı
Basit bir UserService oluşturalım. Bu servis, kullanıcıları getirmek (getUsers) ve yeni bir kullanıcı oluşturmak (createUser) için metotlara sahip olacak.
Servis Yapısı
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'api/users';
constructor(private http: HttpClient) {}
getUsers(): Observable {
return this.http.get(this.apiUrl);
}
createUser(user: User): Observable {
return this.http.post(this.apiUrl, user);
}
}
Test Ortamını Hazırlama
UserService‘i test etmek için TestBed‘i yapılandırmamız gerekir. HttpClientTestingModule‘i imports dizisine ekleyeceğiz ve UserService‘i providers dizisine dahil edeceğiz.
// user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService, User } from './user.service';
describe('UserService', () => {
let service: UserService;
let httpTestingController: HttpTestingController;
const apiUrl = 'api/users';
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // HttpClient'ı taklit etmek için bu modülü içe aktarın
providers: [UserService] // Test edilecek servisi sağlayın
});
// TestBed'den servisi ve HttpTestingController'ı inject edin
service = TestBed.inject(UserService);
httpTestingController = TestBed.inject(HttpTestingController);
});
// Her testten sonra bekleyen HTTP isteklerinin kalmadığından emin olun
afterEach(() => {
httpTestingController.verify();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
// ... test senaryoları buraya gelecek ...
});
beforeEach bloğunda, her testten önce TestBed‘i yapılandırıyoruz ve UserService ile HttpTestingController örneklerini alıyoruz. afterEach bloğundaki httpTestingController.verify() çağrısı, testin sonunda bekleyen hiçbir HTTP isteği kalmadığından emin olmak için önemlidir. Eğer bir istek yapılmadıysa veya bir isteğe yanıt verilmediyse, bu metot bir hata fırlatır ve testin başarısız olmasına neden olur.
GET İsteklerini Test Etmek
UserService‘teki getUsers() metodunu test ederek başlayalım.
Başarılı GET İstekleri
Bu senaryoda, getUsers() metodunun doğru URL’ye bir GET isteği yapıp yapmadığını ve başarılı bir yanıt aldığında verileri düzgün bir şekilde döndürüp döndürmediğini kontrol edeceğiz.
it('should retrieve users from the API via GET', () => {
const mockUsers: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
service.getUsers().subscribe(users => {
// Servisin döndürdüğü verinin mock verilerimizle eşleştiğini kontrol edin
expect(users).toEqual(mockUsers);
});
// HttpTestingController'ı kullanarak beklenen HTTP isteğini yakalayın
const req = httpTestingController.expectOne(apiUrl);
// İsteğin GET metoduyla yapıldığını kontrol edin
expect(req.request.method).toBe('GET');
// Yakalanan isteğe mock verilerle yanıt verin (flush)
req.flush(mockUsers);
});
Yukarıdaki kodda:
1. service.getUsers().subscribe(...) çağrısı ile servis metodunu tetikleriz. Bu, arka planda bir HTTP isteği oluşturur.
2. httpTestingController.expectOne(apiUrl) ile, belirtilen URL’ye yapılan tam olarak bir HTTP isteği bekleriz. Eğer birden fazla veya hiç istek yapılmazsa, bu bir hata fırlatır.
3. expect(req.request.method).toBe('GET') ile isteğin HTTP metodunun GET olduğunu doğrularız.
4. req.flush(mockUsers) ile, yakalanan isteğe mock kullanıcı verileriyle başarılı bir yanıt göndeririz. Bu yanıt, subscribe bloğundaki users değişkenine ulaşır ve burada expect(users).toEqual(mockUsers) ile doğrulanır.
Hatalı GET İstekleri
Servisin bir hata durumunu (örneğin, 500 Internal Server Error) nasıl işlediğini test etmek de önemlidir.
it('should handle GET errors gracefully', () => {
const errorMsg = 'Server Error';
const errorStatus = 500;
service.getUsers().subscribe(
() => fail('should have failed with the 500 error'), // Başarılı olmaması gerektiğini belirtiriz
error => {
expect(error.status).toBe(errorStatus);
expect(error.error).toBe(errorMsg);
}
);
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('GET');
// Hata yanıtı gönderin
req.flush(errorMsg, { status: errorStatus, statusText: 'Internal Server Error' });
});
Burada, req.flush() metoduna bir hata mesajı ve bir HttpErrorResponse‘u taklit eden bir durum nesnesi göndeririz. subscribe metodunun ikinci parametresi olan error callback’i tetiklenir ve hatayı beklendiği gibi işlediğimizi doğrularız.
Birden Fazla GET İstekleri
Bazen aynı URL’ye farklı zamanlarda veya farklı URL’lere aynı anda birden fazla istek yapılması gerekebilir.
it('should handle multiple GET requests to the same URL', () => {
const mockUsers1: User[] = [{ id: 1, name: 'User1', email: 'u1@example.com' }];
const mockUsers2: User[] = [{ id: 2, name: 'User2', email: 'u2@example.com' }];
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers1);
});
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers2);
});
const reqs = httpTestingController.match(apiUrl); // Tüm eşleşen istekleri yakala
expect(reqs.length).toBe(2);
expect(reqs[0].request.method).toBe('GET');
expect(reqs[1].request.method).toBe('GET');
reqs[0].flush(mockUsers1);
reqs[1].flush(mockUsers2);
});
httpTestingController.match(apiUrl) metodu, belirtilen URL’ye yapılan tüm bekleyen istekleri bir dizi olarak döndürür. Bu, birden fazla isteği ayrı ayrı ele almamızı sağlar.
POST, PUT, DELETE İsteklerini Test Etmek
GET isteklerini test etmeye benzer şekilde, POST, PUT ve DELETE gibi diğer HTTP metotlarını da test edebiliriz. Temel fark, expectOne metoduna ikinci bir parametre olarak HTTP metodunu belirtmemiz ve POST/PUT isteklerinde request body’yi kontrol etmemizdir.
POST İstekleri
UserService‘teki createUser() metodunu test edelim.
it('should create a user via POST', () => {
const newUser: User = { id: 3, name: 'Charlie', email: 'charlie@example.com' };
const expectedResponse: User = { ...newUser, id: 101 }; // API'nin döndüreceği ID
service.createUser(newUser).subscribe(user => {
expect(user).toEqual(expectedResponse);
});
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('POST');
// İsteğin body'sinin gönderilen newUser ile eşleştiğini kontrol edin
expect(req.request.body).toEqual(newUser);
req.flush(expectedResponse);
});
Burada, expect(req.request.body).toEqual(newUser) ile isteğin gövdesinin gönderdiğimiz newUser nesnesiyle eşleştiğini doğrularız.
PUT ve DELETE İstekleri
PUT ve DELETE istekleri de benzer bir mantıkla test edilir. Örneğin, bir updateUser veya deleteUser metodunuz olsaydı:
// Örnek bir PUT testi
it('should update a user via PUT', () => {
const updatedUser: User = { id: 1, name: 'Alice Updated', email: 'alice_u@example.com' };
const updateUrl = ${apiUrl}/${updatedUser.id};
// Servisinizde update metodu olduğunu varsayalım: service.updateUser(updatedUser)
service.updateUser(updatedUser).subscribe(user => {
expect(user).toEqual(updatedUser);
});
const req = httpTestingController.expectOne(updateUrl);
expect(req.request.method).toBe('PUT');
expect(req.request.body).toEqual(updatedUser);
req.flush(updatedUser);
});
// Örnek bir DELETE testi
it('should delete a user via DELETE', () => {
const userIdToDelete = 1;
const deleteUrl = ${apiUrl}/${userIdToDelete};
// Servisinizde delete metodu olduğunu varsayalım: service.deleteUser(userIdToDelete)
service.deleteUser(userIdToDelete).subscribe(response => {
expect(response).toBeNull(); // Genellikle DELETE boş yanıt döner
});
const req = httpTestingController.expectOne(deleteUrl);
expect(req.request.method).toBe('DELETE');
req.flush(null); // Boş yanıt gönderin
});
Önemli olan, doğru URL’yi ve doğru HTTP metodunu expectOne ile yakalamak ve ardından beklenen yanıtı flush etmektir.
İstek Parametrelerini ve Başlıklarını Kontrol Etmek
Bazen HTTP isteklerinin sadece URL’sini ve metodunu değil, aynı zamanda sorgu parametrelerini, isteğin gövdesini veya HTTP başlıklarını da kontrol etmemiz gerekir.
Query Parametreleri
Eğer servisiniz sorgu parametreleri kullanıyorsa (örneğin, api/users?page=1&limit=10), bunları da test etmelisiniz.
it('should send query parameters with GET request', () => {
const page = 1;
const limit = 5;
const mockUsers: User[] = [{ id: 1, name: 'Filtered User', email: 'f@example.com' }];
// Varsayalım ki servisinizde getUsers(page, limit) metodu var
service.getUsers(page, limit).subscribe(users => {
expect(users).toEqual(mockUsers);
});
const req = httpTestingController.expectOne(
req => req.url === apiUrl && req.params.get('page') === page.toString() && req.params.get('limit') === limit.toString()
);
expect(req.request.method).toBe('GET');
req.flush(mockUsers);
});
expectOne metoduna bir fonksiyon geçirerek, isteğin URL’sini, parametrelerini veya diğer özelliklerini daha esnek bir şekilde kontrol edebiliriz. req.params.get('paramName') ile sorgu parametrelerine erişebiliriz.
Request Body Kontrolü
POST ve PUT istekleri için request body kontrolünü zaten görmüştük (expect(req.request.body).toEqual(data)). Bu, gönderilen verilerin doğru formatta ve içerikte olduğundan emin olmak için çok önemlidir.
HTTP Başlıkları
Özellikle güvenlik (örneğin, Authorization başlığı) veya içerik tipi (Content-Type) gibi durumlar için HTTP başlıklarını kontrol etmek isteyebilirsiniz.
it('should send custom headers with the request', () => {
const mockUsers: User[] = [];
const customHeaderValue = 'my-custom-token';
// Varsayalım ki servisinizde getUsers(headers) metodu var veya interceptor kullanılıyor
service.getUsersWithCustomHeader(customHeaderValue).subscribe(users => {
expect(users).toEqual(mockUsers);
});
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('GET');
// İsteğin başlıklarını kontrol edin
expect(req.request.headers.get('X-Custom-Header')).toBe(customHeaderValue);
req.flush(mockUsers);
});
req.request.headers.get('Header-Name') ile belirli bir başlığın değerine erişebiliriz.
Interceptor’ları Test Etmek
Angular interceptor’ları, giden HTTP isteklerini değiştirmek veya gelen HTTP yanıtlarını işlemek için güçlü bir mekanizma sunar. Interceptor’ların da doğru çalıştığından emin olmak için test edilmeleri gerekir.
Interceptor’ın Rolü
Bir interceptor, bir HTTP isteği uygulamanızdan ayrılmadan önce veya bir yanıt uygulamanıza ulaşmadan önce araya girer. Yaygın kullanım alanları arasında kimlik doğrulama token’ları eklemek, hata işleme, yükleme göstergeleri yönetmek veya istekleri önbelleğe almak bulunur.
Test Ortamını Hazırlama
Interceptor’ları test ederken, TestBed.configureTestingModule içinde HTTP_INTERCEPTORS token’ını kullanarak interceptor’ınızı sağlamanız gerekir. multi: true ayarı, birden fazla interceptor’ın aynı anda etkin olabileceğini belirtir.
// auth.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest, next: HttpHandler): Observable> {
const authToken = 'fake-auth-token'; // Gerçekte bir servisten alınır
const authReq = req.clone({
headers: req.headers.set('Authorization', Bearer ${authToken})
});
return next.handle(authReq);
}
}
// user.service.spec.ts (AuthInterceptor testi için)
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor'; // Interceptor'ınızı import edin
describe('AuthInterceptor', () => {
let service: UserService;
let httpTestingController: HttpTestingController;
const apiUrl = 'api/users';
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
UserService,
{
provide: HTTP_INTERCEPTORS, // Interceptor'ı sağlayın
useClass: AuthInterceptor,
multi: true
}
]
});
service = TestBed.inject(UserService);
httpTestingController = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpTestingController.verify();
});
it('should add an Authorization header to outgoing requests', () => {
const mockUsers: User[] = [];
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers);
});
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('GET');
// Interceptor'ın eklediği Authorization başlığını kontrol edin
expect(req.request.headers.has('Authorization')).toBeTruthy();
expect(req.request.headers.get('Authorization')).toBe('Bearer fake-auth-token');
req.flush(mockUsers);
});
});
Bu test, AuthInterceptor‘ın UserService tarafından yapılan bir isteğe Authorization başlığını başarıyla eklediğini doğrular. Interceptor’ınızın bir isteği değiştirdiğinden veya bir yanıtı işlediğinden emin olmak için req.request.headers, req.request.body veya req.error gibi özelliklere odaklanmanız gerekir.
Komponentlerdeki HttpClient Kullanımını Test Etmek
Komponentler genellikle servisler aracılığıyla HTTP istekleri yaparlar. Komponentin kendisinin doğrudan HttpClient ile etkileşime girmesini test etmek yerine, genellikle komponentin servislerle olan etkileşimini ve servisten gelen verilere veya hatalara nasıl tepki verdiğini test ederiz.
Senaryo Tanımı
Kullanıcıları listeleyen basit bir UserListComponent oluşturalım. Bu komponent, ngOnInit yaşam döngüsü kancasında UserService‘ten kullanıcıları alacak ve bunları şablonda gösterecektir.
Komponent Yapısı
// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService, User } from './user.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Component({
selector: 'app-user-list',
template:
Users
0; else noUsers">
- {{ user.name }} ({{ user.email }})
No users found.
Loading users...
{{ errorMessage }}
})
export class UserListComponent implements OnInit {
users$: Observable;
errorMessage: string;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.users$ = this.userService.getUsers().pipe(
catchError(error => {
this.errorMessage = 'Failed to load users.';
return of([]); // Hata durumunda boş dizi döndür
})
);
}
}
Test Ortamını Hazırlama
Komponent testlerinde, bağımlılıkları (burada UserService) mocklamak yaygın bir yaklaşımdır. Bu, komponentin birim testini yaparken dış bağımlılıkların (HTTP istekleri gibi) etkisini ortadan kaldırır.
// user-list.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserListComponent } from './user-list.component';
import { UserService, User } from './user.service';
import { of, throwError } from 'rxjs';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture;
let mockUserService: jasmine.SpyObj; // UserService'i mocklamak için SpyObj kullanın
const mockUsers: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
beforeEach(async () => {
// UserService'in mock bir versiyonunu oluşturun
mockUserService = jasmine.createSpyObj('UserService', ['getUsers']);
// getUsers metodunun mockUsers döndürmesini sağlayın
mockUserService.getUsers.and.returnValue(of(mockUsers));
await TestBed.configureTestingModule({
declarations: [UserListComponent],
providers: [
{ provide: UserService, useValue: mockUserService } // Mock servisi sağlayın
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
fixture.detectChanges(); // ngOnInit'i tetikler ve DOM'u günceller
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display users when loaded successfully', () => {
// Kullanıcı listesi elemanlarını kontrol edin
const userItems = fixture.debugElement.queryAll(By.css('li'));
expect(userItems.length).toBe(mockUsers.length);
expect(userItems[0].nativeElement.textContent).toContain('Alice');
expect(userItems[1].nativeElement.textContent).toContain('Bob');
});
it('should display "No users found." when no users are returned', () => {
mockUserService.getUsers.and.returnValue(of([])); // Boş dizi döndür
fixture.detectChanges(); // Komponenti tekrar render edin
const noUsersMessage = fixture.debugElement.query(By.css('ng-template#noUsers'));
expect(noUsersMessage).toBeDefined(); // No users template'inin varlığını kontrol edin.
// DOM'da "No users found." yazısının görünürlüğünü kontrol etmek için daha karmaşık bir yaklaşım gerekebilir,
// çünkü ng-template direkt olarak DOM'da görünmez. Genellikle *ngIf ile kontrol edilir.
// Basit bir test için, listenin boş olup olmadığını kontrol etmek yeterlidir.
const userItems = fixture.debugElement.queryAll(By.css('li'));
expect(userItems.length).toBe(0);
});
it('should display an error message when user loading fails', () => {
// Hata durumunu simüle edin
mockUserService.getUsers.and.returnValue(throwError({ status: 500, message: 'Server error' }));
fixture.detectChanges(); // Komponenti tekrar render edin
const errorMessage = fixture.debugElement.query(By.css('.error'));
expect(errorMessage).toBeTruthy();
expect(errorMessage.nativeElement.textContent).toContain('Failed to load users.');
});
});
Servis Mock’lama Yaklaşımı
Yukarıdaki örnekte, UserService‘i bir jasmine.SpyObj kullanarak mock’ladık. Bu, getUsers gibi metotların gerçek HTTP isteği yapmadan doğrudan test verilerini döndürmesini sağlar. Bu, komponentin izole bir şekilde test edilmesini ve testlerin hızlı çalışmasını sağlar. fixture.detectChanges() çağrısı, Angular’ın değişiklik algılama döngüsünü tetikler ve şablonun güncellenmesini sağlar.
Gerçek Servis ile Entegrasyon Testi
Bazı durumlarda, komponentin bir servisle ve HttpClientTestingModule ile gerçekten entegre olduğunu test etmek isteyebilirsiniz. Bu, daha çok bir entegrasyon testi senaryosudur.
// user-list.component.spec.ts (HttpClientTestingModule ile entegrasyon testi)
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserListComponent } from './user-list.component';
import { UserService, User } from './user.service';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
describe('UserListComponent - Integration with HttpTestingController', () => {
let component: UserListComponent;
let fixture: ComponentFixture;
let httpTestingController: HttpTestingController;
const apiUrl = 'api/users';
const mockUsers: User[] = [
{ id: 1, name: 'Charlie', email: 'charlie@example.com' },
{ id: 2, name: 'Diana', email: 'diana@example.com' }
];
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // HttpClientTestingModule'i import edin
declarations: [UserListComponent],
providers: [UserService] // Gerçek UserService'i sağlayın
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
httpTestingController = TestBed.inject(HttpTestingController);
// İlk fixture.detectChanges() çağrısı ngOnInit'i tetikler ve HTTP isteğini başlatır.
fixture.detectChanges();
});
afterEach(() => {
httpTestingController.verify();
});
it('should display users loaded via HttpClientTestingModule', () => {
// UserService tarafından yapılan GET isteğini yakalayın
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('GET');
// İsteğe mock verilerle yanıt verin
req.flush(mockUsers);
// Yanıt geldikten sonra komponentin DOM'unu güncellemek için tekrar detectChanges() çağırın
fixture.detectChanges();
const userItems = fixture.debugElement.queryAll(By.css('li'));
expect(userItems.length).toBe(mockUsers.length);
expect(userItems[0].nativeElement.textContent).toContain('Charlie');
});
it('should display error message on http failure', () => {
const errorMsg = 'Failed to fetch users';
const req = httpTestingController.expectOne(apiUrl);
expect(req.request.method).toBe('GET');
req.flush(errorMsg, { status: 500, statusText: 'Server Error' });
fixture.detectChanges();
const errorMessage = fixture.debugElement.query(By.css('.error'));
expect(errorMessage).toBeTruthy();
expect(errorMessage.nativeElement.textContent).toContain('Failed to load users.');
});
});
Bu entegrasyon testinde, UserService‘in kendisini mock’lamak yerine, HttpClientTestingModule‘i kullanarak gerçek UserService‘in HttpClient üzerinden yaptığı isteği yakalayıp kontrol ederiz. Bu, komponent ile servisin birlikte nasıl çalıştığını daha gerçekçi bir şekilde test etmemizi sağlar.
İpuçları ve En İyi Uygulamalar
HttpClient isteklerini test ederken, testlerinizin verimli, güvenilir ve sürdürülebilir olmasını sağlamak için bazı en iyi uygulamaları takip etmek önemlidir.
Her Zaman httpTestingController.verify() Kullanın
afterEach bloğunda httpTestingController.verify() metodunu çağırmak, testin sonunda bekleyen hiçbir HTTP isteği kalmadığından emin olmanın en iyi yoludur. Bu, testlerinizin temiz kalmasına ve gizli hataları yakalamanıza yardımcı olur. Unutulan veya yanlış eşleşen bir istek varsa, verify() bir hata fırlatacaktır.
Testleri İzole Edin
Her it bloğu bağımsız olmalı ve diğer testlerin sonuçlarından etkilenmemelidir. beforeEach ve afterEach blokları, her test için temiz bir başlangıç ve bitiş sağlamak için kullanılır. Mock veriler ve servis örnekleri her testten önce yeniden oluşturulmalıdır.
Anlamlı Mock Verileri Kullanın
Testleriniz için kullandığınız mock verilerin, gerçek API’den bekleyeceğiniz verilere mümkün olduğunca yakın olduğundan emin olun. Bu, testlerinizin gerçek dünya senaryolarını daha iyi yansıtmasını sağlar. Edge case’leri (boş listeler, eksik alanlar vb.) de test etmek için farklı mock veriler kullanın.
Edge Case’leri Test Edin
Sadece başarılı senaryoları değil, aynı zamanda hata durumlarını (ağ hataları, 404 Not Found, 500 Internal Server Error), boş yanıtları veya beklenmedik veri formatlarını da test edin. Servisinizin veya komponentinizin bu durumları nasıl işlediğini doğrulamak, uygulamanızın sağlamlığını artırır.
Asenkron İşlemleri Yönetin
HTTP istekleri asenkrondur. Jasmine’in done callback’ini, async/await ile fakeAsync/tick yardımcılarını veya RxJS operatörlerini (örneğin firstValueFrom veya lastValueFrom) kullanarak asenkron testleri düzgün bir şekilde yönettiğinizden emin olun. fakeAsync ve tick genellikle daha okunabilir ve kontrol edilebilir testler sağlar.
// fakeAsync ile bir örnek
import { fakeAsync, tick } from '@angular/core/testing';
it('should load users with fakeAsync', fakeAsync(() => {
const mockUsers: User[] = [{ id: 1, name: 'Fake User', email: 'fake@example.com' }];
let actualUsers: User[] = [];
service.getUsers().subscribe(users => {
actualUsers = users;
});
const req = httpTestingController.expectOne(apiUrl);
req.flush(mockUsers); // İstek yanıtını gönder
tick(); // Tüm bekleyen asenkron işlemleri tamamla
expect(actualUsers).toEqual(mockUsers);
}));
Test Kapsamını Belirleyin
Bir servisi mi yoksa bir komponenti mi test ettiğinize bağlı olarak farklı yaklaşımlar benimseyin:
* Servis Birim Testleri: HttpClientTestingModule ve HttpTestingController kullanarak servisin HttpClient ile doğru etkileşime girdiğini doğrulayın. Servisin dış bağımlılıklarını mock’lamayın.
* Komponent Birim Testleri: Komponentin bağımlı olduğu servisleri (örneğin UserService) mock’layın. Komponentin kendi mantığını ve şablonla etkileşimini test edin. Servislerin iç işleyişi bu testin konusu değildir.
* Entegrasyon Testleri: Komponent ve servisin birlikte nasıl çalıştığını test etmek için HttpClientTestingModule‘i kullanarak gerçek servis örneğini kullanabilirsiniz. Bu, daha az yaygın olsa da, belirli senaryolarda faydalı olabilir.
Sonuç
Angular’da HttpClient isteklerini test etmek, uygulamanızın güvenilirliğini ve kararlılığını sağlamak için vazgeçilmez bir adımdır. HttpClientTestingModule ve HttpTestingController gibi güçlü araçlar sayesinde, arka uç servislerine bağımlı olmadan HTTP isteklerini tamamen kontrol edebilir ve izole bir ortamda test edebilirsiniz. Servislerinizi, interceptor’larınızı ve komponentlerinizi kapsamlı bir şekilde test ederek, uygulamanızın beklenmedik durumlarla başa çıkabildiğinden ve kullanıcılarınıza sorunsuz bir deneyim sunduğundan emin olabilirsiniz. Sağlam testler, uzun vadede geliştirme sürecini hızlandırır ve bakım maliyetlerini düşürür.
