مشکلات رایج در تست اجزای زاویه ای
وقتی در مورد تست واحد صحبت می کنیم، اولین تعریفی که به ذهن ما می رسد این است که بخش های کوچک قابل آزمایش یک برنامه مانند توابع، متدها و کلاس ها را آزمایش کنیم. برای یک تابع ساده یا کلاس ایزوله بدون وابستگی عالی به نظر می رسد، اما اینطور است نه در دنیای واقعی.
در انگولار کامپوننت ها دارای دکوراتورها، سرویس ها، لوله ها، قالب هایی برای رندر اطلاعات هستند، و گاهی اوقات کامپوننت های فرزند، بنابراین ما بازیگران زیادی در آزمون خود داریم.
این مقاله بر مشکلات یا مسائل معمولی تمرکز میکند که وقتی شروع به اضافه کردن آزمایش در یک مؤلفه با وابستگیهایی مانند خدمات و مؤلفههای فرزند میکنیم، با آنها مواجه میشویم.
سناریو
ما یک برنامه داریم که لیستی از بازیکنان NBA را از API با استفاده از آن نشان می دهد PlayerService
; این برنامه با دو جزء اصلی کار می کند.
پخش کننده: جزء گنگ برای نمایش اطلاعات تک نفره.
PlayerListComponent: از لیست بازیکنان استفاده می کند
PlayerService\
برای رندر در نما با استفاده از مؤلفه پخش کننده.
ما قصد داریم آن را آزمایش کنیم playerlist.component.ts
رسیدگی به وابستگی های خدمات و مؤلفه فرزند.
تایپ اسکریپت:
import { Component, OnInit } from '@angular/core';
import { PlayerService } from 'src/app/services/players.services';
import { APIResponse, Player } from '../models/player';
@Component({
selector: 'app-player-list',
templateUrl: './player-list.component.html',
styleUrls: ['./player-list.component.css'],
})
export class PlayerListComponent implements OnInit {
players: Player[] = [];
constructor(private playerService: PlayerService) { }
ngOnInit(): void {
this.playerService.getPlayers().subscribe((players: APIResponse) => {
this.players = players.data;
});
}
}
نشانه گذاری HTML از <app-player>
جزء.
<div *ngFor="let player of players" >
<app-player [player]="player"></app-player>
</div>
بیایید سعی کنیم کامپوننت را تست کنیم!
پایه یاس
قبل از شروع، به یاد داشته باشید که Angular به Jasmine و Karma به عنوان دونده آزمون و تست های ما متکی است. اگر هرگز تستی نمی نویسید، کوتاه و مقدمه است.
describe
: تست های مرتبط با گروه با هم. یک رشته و یک تابع را به عنوان آرگومان می گیرد.beforeEach
: قبل از هر تست برای عملیات راه اندازی اجرا می شود.it
: یک مورد آزمایشی را تعریف می کند. یک رشته و یک تابع را به عنوان آرگومان می گیرد.expect
: برای اثبات رفتار کد مورد آزمایش استفاده می شود.
درباره یاس و کارما بیشتر بدانید
Testbed چیست
بستر آزمایشی یک محیط تست قدرتمند و با کاربری آسان است که به ما امکان میدهد اجزا و سرویسها را به صورت مجزا و بدون وابستگی به بقیه برنامهها آزمایش کنیم. مجموعه ای از API ها را برای ایجاد و پیکربندی محیط آزمایش و تعامل با مؤلفه یا سرویس آزمایش شده ارائه می دهد.
با استفاده از بستر آزمایش، از دو روش استفاده می کنیم: configureTestingModule
و createComponent
.
برای پیکربندی یک تخت تست برای یک ماژول یا جزء خاص، از آن استفاده می کنیم configureTestingModule()
متد یک شی را به عنوان آرگومان برای پیکربندی ارائهدهندگان، اعلانها، واردات و سایر گزینههای مشابه میگیرد. app.module.ts.
بیشتر بدانید بستر آزمایش
این createComponent
نمونه ای از یک جزء ایجاد می کند و a را برمی گرداند ComponentFixture
برای آن مؤلفه، امکان آزمایش رفتار مؤلفه و تعاملات DOM را فراهم می کند.
ما ایجاد می کنیم player-list.component.spec.ts
، اضافه کردن describe
با عنوان آزمون
describe('PlayerList Component', () => {
})
متغیر ثابت را برای ذخیره کامپوننت با استفاده از آن اعلام کنید ComponentFixture
، و متغیر را با استفاده از نوع تنظیم کنید PlayerListComponent
.
describe('PlayerList Component', () => {
let fixture: ComponentFixture<PlayerListComponent>
});
زمان استفاده از BeforeEach
و TestBed برای پیکربندی ماژول تست ما، همانطور که قبل از آن یاد گرفتیم configureTestingModule
به ما در راه اندازی ماژول کمک کنید.
را اعلام کنید PlayerListComponent
و با TestBed.createComponent
برای ایجاد نمونه از PlayerListComponent
.
کد نهایی به شکل زیر است:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PlayerListComponent } from './player-list.component';
describe('PlayerList Component', () => {
let fixture: ComponentFixture<PlayerListComponent>
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [PlayerListComponent]
})
fixture = TestBed.createComponent(PlayerListComponent);
})
it('should render the component', () => {
expect(true).toBe(true)
})
})
تغییرات را ذخیره کنید و دستور را اجرا کنید: npm run test
:
✔ Browser application bundle generation complete.
Chrome 108.0.0.0 (Windows 10) PlayerList Component should render the component FAILED
NullInjectorError: R3InjectorError(DynamicTestModule)[PlayerService -> PlayerService]:
NullInjectorError: No provider for PlayerService!
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'PlayerService', 'PlayerService' ] })
Chrome 108.0.0.0 (Windows 10): Executed 1 of 1 (1 FAILED) (0.046 secs / 0.04 secs)
TOTAL: 1 FAILED, 0 SUCCESS
ما یک خطا دریافت کردیم زیرا کامپوننت از آن استفاده می کند PlayerService
در سازنده، بنابراین باید آن را ارائه کنیم PlayerService
بدون درخواست واقعی
با استفاده از تابع، ما را نجات دهید createSpyObj
از jasmine، ما میتوانیم اشیاء را با روشهای جاسوسی مسخره کنیم و به شما این امکان را میدهد تا رفتار عملکردهای خاص را بدون اجرای واقعی ردیابی و آزمایش کنید.
یک متغیر جدید اعلام کنید mockPlayerService
استفاده كردن jasmine.createSpyObj
، مجبور شد روش ها را به Mock اعلام کند. در مورد ما، getPlayers
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PlayerService } from 'src/app/services/players.services';
import { PlayerListComponent } from './player-list.component';
describe('PlayerList Component', () => {
let fixture: ComponentFixture<PlayerListComponent>
const mockPlayerService = jasmine.createSpyObj<PlayerService>(["getPlayers"]);
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [PlayerListComponent],
providers: [{
provide: PlayerService,
useValue: mockPlayerService
}]
})
fixture = TestBed.createComponent(PlayerListComponent);
})
it('should render the component', () => {
expect(true).toBe(true)
})
})
همه چیز سبز است! بنابراین می توانیم شروع به نوشتن تست خود کنیم.
بیشتر بدانید مسخره و جاسوس
تست کامپوننت
ما ماژول تست را آماده داریم. ما برای کارهای زیر آماده هستیم تا با انجام کارهای زیر کامپوننت را تست کنیم.
- [x] جعل داده های پاسخ
- [x] یک تست برای مؤلفه دریافت بازیکنان از سرویس ایجاد کنید
- [x] مولفه را فعال کنید
change-detection
. - [x] دارایی را اعتبار سنجی کنید
players
دارای ارزش از پاسخ جعلی است.
اجازه دهید یک تست برای جذب بازیکنان ایجاد کنید از خدمات ما یک متغیر با ساختار از backend ایجاد می کنیم تا از آن به عنوان پاسخ داده جعلی استفاده کنیم:
const FAKE_API_RESPONSE: APIResponse = {
data: [{
id: 1,
first_name: 'Lebron'
}]
};
یک تست برای دریافت بازیکنان از سرویس ایجاد کنید با it
باید بازیکنان را از خدمات و استفاده از آنها دریافت کند and.returnValue
با ساختگی برای بازگشت قابل مشاهده به عنوان یک پاسخ جعلی، باید تشخیص تغییر را فعال کنید استفاده كردن fixture.detectChanges
برای شروع چرخه حیات تشخیص تغییر در کامپوننت.
it('should get players from service', () => {
mockPlayerService.getPlayers.and.returnValue(of(FAKE_API_RESPONSE));
fixture.detectChanges();
})
ذخیره و … DAMMMM!!! یک خطای جدید گرفتیم!!!
PlayerList Component > should get players from the service
Error: NG0304: 'app-player' is not a known element (used in the 'PlayerListComponent' component template):
1. If 'app-player' is an Angular component, then verify that it is a part of an @NgModule where this component is declared.
2. If 'app-player' is a Web Component, then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
چرا خطا: NG0304: ‘app-player’ یک عنصر شناخته شده نیست؟
لیست پخش از مؤلفه ای استفاده می کند که در ماژول آزمایشی ما ثبت نشده است. چه گزینه ای دارم؟
این schemas
ملک در @NgModule
دکوراتور اعتبار الگو را با استفاده از NO_ERRORS_SCHEMA
ثابت که اعتبار سنجی الگو را به طور کامل غیرفعال می کند.
بیایید موارد زیر را امتحان کنیم:
....CODE COLLAPSED
TestBed.configureTestingModule({
declarations: [PlayerListComponent],
providers: [
{
provide: PlayerService,
useValue: mockPlayerService,
},
],
schemas: [NO_ERRORS_SCHEMA],
});
fixture = TestBed.createComponent(PlayerListComponent);
});
it('should get players from service', () => {
mockPlayerService.getPlayers.and.returnValue(of(FAKE_API_RESPONSE));
fixture.detectChanges();
});
});
توجه: رویکردهای بهتری نسبت به نادیده گرفتن مسائل قالب وجود دارد. بعداً راه دیگری برای رسیدگی به آنها پیدا خواهیم کرد.
ذخیره، و … آره، دوباره سبز. بیایید به اعتبارسنجی ارزش دارایی از پاسخ جعلی ادامه دهیم.
چگونه مطمئن شویم که بازیکن دارایی ارزش دارد؟ فیکسچر چند راه برای دسترسی به مؤلفه را نشان می دهد:
componentInstance
: نمونه ای از مؤلفه آزمایش شده را برمی گرداند. ما می توانیم از این ویژگی برای دسترسی به ویژگی ها و متدهای کامپوننت و بررسی مقادیر آنها استفاده کنیم.debugElement
: نمونه ای ازDebugElement
جزء تست شده اینDebugElement
یک پوشش در اطراف عنصر بومی است و به شما امکان می دهد با DOM کامپوننت به گونه ای تعامل داشته باشید که برای پلتفرم زیربنایی آگنوستیک باشد.debugElement.nativeElement
: عنصر اصلی مؤلفه آزمایش شده، عنصر واقعی در DOM را برمی گرداند. میتوانیم از این ویژگی برای تعامل با DOM مؤلفه بهطور غیرمستقیم استفاده کنیم و ویژگیها، سبکها و محتوای آن را بررسی کنیم.
بیایید استفاده کنیم componentInstance
برای دسترسی به ویژگی بازیکنان، با استفاده از expect
برای اثبات مقادیر، FAKE_API_REPONSE
، یک آیتم را برگردانید و بررسی کنید که طول بازیکنان برابر با 1 باشد.
it('should get players from service', () => {
mockPlayerService.getPlayers.and.returnValue(of(FAKE_API_RESPONSE));
fixture.detectChanges();
expect(fixture.componentInstance.players.length).toEqual(1)
});
ذخیره و نتایج:
کار با اجزای کودک
یادتان هست که الگو را نادیده گرفتیم؟ آن راه حل فقط یک پچ بود. اگر میخواهیم مطمئن شویم که کامپوننت ما بازیکنان را رندر میکند، باید تأیید کنیم که الگو کار میکند.
مشابه کاری که با این سرویس انجام دادیم، می توانیم آن را مسخره کنیم <app-player>
جزء، بنابراین حذف کنید schemas: [NO_ERRORS_SCHEMA]
.
برای مسخره کردن کامپوننت، یک کامپوننت جدید را در تست با همان انتخابگر اعلام می کنیم app-player
و خواص مورد نیاز آزمایش ما. در قالب، کلاس CSS را اضافه کنید player
تا پیدا کردن عناصر آسان شود.
@Component({
selector: 'app-player',
template: `<div class="player">
<span>{{player.name}}</span>
</div>`
})
class MockPlayer {
@Input() player!: Player;
}
بعد، ثبت نام کنید MockPlayer
در بخش اعلامیه ها:
TestBed.configureTestingModule({
declarations: [PlayerListComponent, MockPlayer],
providers: [
{
provide: PlayerService,
useValue: mockPlayerService,
},
]
});
چه کار کردیم؟
ما یک مؤلفه Mock را با همان انتخابگر ارائه می دهیم و در ماژول تست ثبت می کنیم. هنگامی که کامپوننت
کامپوننت های Child Render را تست کنید
ابتدا، مشابه آزمایش دیگر ما، باید داده های جعلی را اختصاص دهیم و تشخیص تغییر را آغاز کنیم.
برای یافتن و تعامل با کامپوننت، به چند روش از فیکسچر کامپوننت می پردازیم.
queryAll
آرایه ای از DebugElements را برمی گرداند که با یک گزاره داده شده مطابقت دارند.
by.css
یک محمول queryAll
برای یافتن عناصری که با یک انتخابگر CSS مطابقت دارند.
by.directive
به عنوان محمول استفاده کنید queryAll
برای پیدا کردن عناصری که با یک دستورالعمل مشخص شده اند.
این debugElement
یک شی است که دسترسی به عنصر اصلی و نمونه مؤلفه مرتبط با یک عنصر DOM داده شده در یک فیکسچر آزمایشی را فراهم می کند.
از آنجا که ما یک کلاس CSS را در مؤلفه ساختگی اضافه می کنیم، از By.css برای پرس و جوی همه عناصر با کلاس استفاده می کنیم. div.player
رندر در DOM
یک متغیر جدید ایجاد کنید totalPlayers
و نتیجه را ذخیره کنید queryAll
که در totalPlayer
آرایه ای را برمی گرداند، از totalPlayer.length
انتظار آزمون
it('should render the players', () => {
mockPlayerService.getPlayers.and.returnValue(of(FAKE_API_RESPONSE));
fixture.detectChanges();
const totalPlayers = fixture.debugElement.queryAll(By.css('div.player')
expect(totalPlayers).length).toBe(1);
})
ذخیره کنید، و آزمایشات ما با استفاده از یک سرویس و الگوی ساختگی قبول می شوند.
توجه داشته باشید: جایگزین دیگر با By.directive
با استفاده از نام کلاس کامپوننت
expect(fixture.debugElement.queryAll(By.directive(MockPlayer)).length).toBe(1);
انجام شده! ما قبلاً مؤلفه خود را آزمایش کرده ایم:
- [x] ما مؤلفه را تأیید می کنیم تا داده ها را دریافت کنیم.
- [x] ما تأیید می کنیم که تاریخ در DOM است.
- [x] ما یاد می گیریم که سرویس ها و کامپوننت ها را تقلید کنیم
- [x] ما یاد می گیریم که عناصر را در DOM با استفاده از By پرس و جو کنیم
نتیجه
هنگام تست کامپوننت ها در Angular با وابستگی های متعدد، مشکلات رایج را یاد می گیریم. تست مولفهها با بستر تست، سرویسهای تمسخرآمیز و اجزای فرزند بخش مهمی از اطمینان از استحکام و قابلیت اطمینان برنامه شما است.
سرویسهای مسخره و مؤلفههای فرزند شما را قادر میسازد که ورودی و خروجی آن وابستگیها را کنترل کنید و آزمایش مولفه را به صورت مجزا آسانتر میکند.
این تکنیک ها می توانند استراتژی تست شما را بهبود بخشند و برنامه های قابل اعتمادتر و قابل نگهداری بیشتری بسازند.
اگر دوست داشتید لطفا به اشتراک بگذارید 🙂
عکس لویی رید در Unsplash