Ana içeriğe geç

Angular uygulamasında bir yönlendirme koruyucusunu nasıl test edilir

Eğer "Bir yön dizesinin nasıl test edileceğini" okumadıysanız, lütfen önce bunu yapın.

bilgi

Bir koruyucuyu test etmek, koruyucu ve RouterModule haricindeki her şeyi sahte bir şekilde simüle etmemiz gerektiği anlamına gelir.

Ama, ya birden fazla koruyucumuz varsa? Eğer onları sahteleyip mock yaparsak, yanlış dönen mock metodları nedeniyle yolları engellerler.
Angular testlerinde koruyucuları kaldırmak için ng-mocks NG_MOCKS_GUARDS token'ını sağlar, bunu .exclude içerisine geçmeliyiz, ardından diğer tüm koruyucular TestBed'den dışlanacak ve sadece istediğimiz koruyucuyu test ettiğimizden emin olabileceğiz.

Aşağıdaki örnek tüm koruyucu türleri için geçerlidir:


Fonksiyonel Koruyucular

Fonksiyonel bir koruyucu, basit bir fonksiyondur ve Angular 14 öncesinde olduğu gibi bir servis veya token değildir.
Bir koruyucu, RouterModule.forRoot veya RouterModule.forChild şeklinde bir modülde tanımlanan yolların yapılandırmasında yer alır.

ipucu

Bir koruyucu test etmek için, koruyucuyu ve koruyucunun tanımlandığı bir modülü sağlamanız gerekmektedir. Kolaylık olması açısından koruyucuyu loginGuard ve modülü TargetModule olarak adlandıralım.

Koruyucu, diğer koruyucuların yan etkilerinden kaçınmak için izole bir şekilde test edilmelidir.
Ayrıca, RouterModule ve onun bağımlılıkları, koruyucunun yoluna doğru bir şekilde bağlandığından emin olmak için bir testte sağlanmalıdır ve Location ve/veya Router üzerinde doğrulama yapabilirsiniz.
Geri kalan kısmı sahtelemeler olabilir.

beforeEach(() =>
MockBuilder(
// ilk parametre
// RouterModule ve bağımlılıklarını sağlamak
[
RouterModule,
RouterTestingModule.withRoutes([]),
NG_MOCKS_ROOT_PROVIDERS,
],

// ikinci parametre
// TargetModule'in sahte tanımı
TargetModule,
)

// zincir
// yan etkileri önlemek için tüm koruyucuları hariç tutuyoruz
.exclude(NG_MOCKS_GUARDS)

// zincir
// test için loginGuard'ı koruyoruz
.keep(loginGuard)
);

Koruyucunun, kullanıcı giriş yapmadığı takdirde tüm yolları /login adresine yönlendirdiğini varsayalım.
Yani uygulama başlatıldığında yönlendirme /login'da bitmelidir.

Bunu doğrulayalım:

  1. bir yönlendirme çıkışı render et
  2. yönlendirmeyi başlat
  3. konumu doğrula

Bir yönlendirme çıkışı render etmek için MockRender'ı boş parametrelerle kullanabilirsiniz.

const fixture = MockRender(RouterOutlet, {});

Artık Router ve Location'ı alabilirsiniz.
İlki, başlatma için gerekir, ikincisi ise doğrulama için.

const router = ngMocks.get(Router);
const location = ngMocks.get(Location);

Yönlendirmeyi başlatmak için router.initialNavigation çağrısını yapmanız ve ardından tick ile yolun başlatıldığından ve render edildiğinden emin olmanız gerekir.

if (fixture.ngZone) {
fixture.ngZone.run(() => router.initialNavigation());
tick(); // mevcut yolun render edilmesi için gereklidir.
}

Artık konumu doğrulayabilirsiniz.

expect(location.path()).toEqual('/login');
tehlike

Kar, fonksiyonel bir koruyucu için bir test örneği olacaktır.

Sınıf Koruyucular (eski)

Kodunuzda sınıf olan ve Angular servisleri ile koruyucular varsa, süreç tam olarak fonksiyonel koruyucular ile aynı olacaktır.

Örneğin, koruyucunun sınıfı LoginGuard olarak adlandırılmışsa, TestBed'in yapılandırması şu şekilde olmalıdır:

beforeEach(() =>
MockBuilder(
// ilk parametre
// RouterModule ve bağımlılıklarını sağlamak
[
RouterModule,
RouterTestingModule.withRoutes([]),
NG_MOCKS_ROOT_PROVIDERS,
],

// ikinci parametre
// TargetModule'in sahte tanımı
TargetModule,
)

// zincir
// yan etkileri önlemek için tüm koruyucuları hariç tutuyoruz
.exclude(NG_MOCKS_GUARDS)

// zincir
// test için LoginGuard'ı koruyoruz
.keep(LoginGuard)
);

Kar.

Canlı örnek

https://github.com/help-me-mom/ng-mocks/blob/master/examples/TestRoutingGuard/can-activate.spec.ts
import { Location } from '@angular/common';
import {
Component,
inject,
Injectable,
NgModule,
} from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing';
import {
CanActivateFn,
Router,
RouterModule,
RouterOutlet,
} from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { from } from 'rxjs';
import { mapTo } from 'rxjs/operators';

import {
MockBuilder,
MockRender,
NG_MOCKS_GUARDS,
NG_MOCKS_ROOT_PROVIDERS,
ngMocks,
} from 'ng-mocks';

// Giriş kontrolünü simüle eden basit bir servis.
// Bu, mock kopyasıyla değiştirilecektir.
@Injectable()
class LoginService {
public isLoggedIn = false;
}

// Test etmek istediğimiz koruyucu.
const canActivateGuard: CanActivateFn = (route, state) => {
if (route && state && inject(LoginService).isLoggedIn) {
return true;
}

return from(inject(Router).navigate(['/login'])).pipe(mapTo(false));
};

// Gerçek bir dünya örneği gibi başka bir koruyucu.
// Koruyucu, yol üzerinde yan etkilere neden olmamak için testten çıkarılmalıdır.
const sideEffectGuard: CanActivateFn = () => false;

// Giriş formu gibi davranan basit bir bileşen.
// Bu, mock kopyasıyla değiştirilecektir.
@Component({
selector: 'login',
template: 'login',
})
class LoginComponent {
public loginTestRoutingGuardCanActivate() {}
}

// Korunmalı bir gösterge panosuna benzer basit bir bileşen.
// Bu, mock kopyasıyla değiştirilecektir.
@Component({
selector: 'dashboard',
template: 'dashboard',
})
class DashboardComponent {
public dashboardTestRoutingGuardCanActivate() {}
}

// Yönlendirme modülünün tanımı.
@NgModule({
declarations: [LoginComponent, DashboardComponent],
exports: [RouterModule],
imports: [
RouterModule.forRoot([
{
component: LoginComponent,
path: 'login',
},
{
canActivate: [canActivateGuard, sideEffectGuard],
component: DashboardComponent,
path: '**',
},
]),
],
providers: [LoginService],
})
class TargetModule {}

describe('TestRoutingGuard:canActivate', () => {
// Bir canActive koruyucusunu test etmek istediğimiz için, bu
// RouterModule ile entegrasyonunu test etmek istediğimiz anlamına gelir.
// Bu nedenle, RouterModule ve koruyucu tutulmalı,
// ve yolu tanımlayan modülün geri kalanı sahtelemelidir.
// Test için RouterModule'u yapılandırmak üzere,
// RouterModule, RouterTestingModule.withRoutes([]), NG_MOCKS_ROOT_PROVIDERS
// MockBuilder'ın ilk parametresi olarak belirtilmelidir (evet, boş yollarla).
// Yollar ve koruyucu olan modül, MockBuilder'ın
// ikinci parametresi olarak belirtilmelidir.
// Ardından, tüm koruyucuları kaldırmak için `NG_MOCKS_GUARDS` hariç tutulmalı
// ve `canActivateGuard` test edebilmeniz için tutulmalıdır.
beforeEach(() => {
return MockBuilder(
[
RouterModule,
RouterTestingModule.withRoutes([]),
NG_MOCKS_ROOT_PROVIDERS,
],
TargetModule,
)
.exclude(NG_MOCKS_GUARDS)
.keep(canActivateGuard);
});

// Yönlendirme testlerini fakeAsync içinde çalıştırmak önemlidir.
it('girişe yönlendirir', fakeAsync(() => {
const fixture = MockRender(RouterOutlet, {});
const router = ngMocks.get(Router);
const location = ngMocks.get(Location);

// Önce yönlendirmeyi başlatmamız gerekiyor.
if (fixture.ngZone) {
fixture.ngZone.run(() => router.initialNavigation());
tick(); // mevcut yolun render edilmesi için gereklidir.
}

// Çünkü varsayılan olarak giriş yapmadığımız için, koruyucu
// bizi /login sayfasına yönlendirmelidir.
expect(location.path()).toEqual('/login');
expect(() => ngMocks.find(LoginComponent)).not.toThrow();
}));

it('gösterge panosunu yükler', fakeAsync(() => {
const fixture = MockRender(RouterOutlet, {});
const router = ngMocks.get(Router);
const location = ngMocks.get(Location);
const loginService = ngMocks.get(LoginService);

// Koruyucuya giriş yaptığımızı bildirmek.
loginService.isLoggedIn = true;

// Önce yönlendirmeyi başlatmamız gerekiyor.
if (fixture.ngZone) {
fixture.ngZone.run(() => router.initialNavigation());
tick(); // mevcut yolun render edilmesi için gereklidir.
}

// Artık giriş yaptığımız için, koruyucu bizi
// gösterge panosuna yönlendirmelidir.
expect(location.path()).toEqual('/');
expect(() => ngMocks.find(DashboardComponent)).not.toThrow();
}));
});