همه چیز درباره unit test در Angular - بخش سوم

در این سری مقالات درباره تست های Angular صحبت میکنیم.در بخش اول از کلیات تست گفتیم و برای پایپ ها، سرویس ها و کامپوننت ها isolated test نوشتیم.در بخش دوم تست های shallow integrated رو برای کامپوننت نوشتیم.در این بخش میخوایم تست های deep integration رو برای کامپوننت مون بنویسیم.نمونه کد های این سری مقالات رو میتونید در گیت هاب مطالعه کنید یا پروژه رو دانلود و نصب کنید.آدرس گیت هاب این سری مقالات : https://github.com/rohamtehrani/angular-unit-testsتست های deep integration در واقع تست کامپوننت و dependency های واقعی شون هستند.فرض کنیم که میخوایم برای کامپوننت HeroesComponent تست deep integration بنویسیم.اول یه نگاهی به کد کامپوننت بندازیم:و بعد یه نگاهی به تمپلت بندازیم:یک فایل جدیدی میسازیم به اسم heroes.component.deep.spec.tsدر تست deep integration میخوایم ارتباط کامپوننت با وابستگی های واقعی رو تست بگیریم. میتونیم تصمیم بگیریم که کدوم وابستگی ها واقعی باشند و کدوم فیک. در تست اول میخوایم ببینیم که آیا المنت های hero component درست رندر میشن یا خیر. پس نیاز به child component واقعی داریم و سرویس رو mock شده استفاده می کنیم که موقع تست درگیرش نشیم.شرایط اولیه قبل از هر تست رو با استفاده از HeroComponent واقعی پیاده سازی میکنیم:حالا بریم تست اول مون رو بنویسیم و با By.directive آشنا بشیم. میدونیم که کامپوننت ها در واقع نوعی از directive ها هستند که تمپلت دارند. تست اول مون رو به صورت زیر مینویسیم تا ببینیم به ازای دریافت تعداد مشخصی hero، به همان تعداد کامپوننت HeroComponent ساخته میشه یا خیر:و تست نشون میده که مشکلی نیست و به تعداد Hero ها کامپوننت HeroComponent ساخته شده.نکته بعدی دسترسی به instance مربوط به کامپوننت هایی هست که در طول تست ساخته شده اند. مثلا دسترسی به اولین intance ساخته شده از HeroCompoenent به صورت زیر هست:بنابر این میتونیم یک بخش دیگه به تست مون اضافه کنیم و ببینیم محتوای کامپوننت HeroComponent درست ست شده یا نه:فرمت کد صرفا جهت جاشدن کدها در فریم اسکرین شات اینطوریه!اینکه آیا تست کردن دیتای کامپوننت HeroComponent مربوط به تست HeroesComponent میشه یا باید در تست های خودش بررسی بشه یا هر دوجا بررسی بشه یا اصلا نیازی به تستش نیست ، این تصمیمیه که هر برنامه نویسی موقع نوشتن تست و کد میگیره. الزاما برتری خاصی بینشون نیست. هدف ارائه کدی هست که باگ های احتمالی فعلی داخلش شناسایی بشه و از باگ های احتمالی آینده جلوگیری کنه.من به شخصه وقتی از نوشتن تست لذت میبرم که تستم مچ پیاده سازیم رو بگیره. یعنی به یک موضوعی موقع پیاده سازی کد دقت نکردم، ولی تستی که نوشتم خطا داده.سرویس HeroService رو در نظر می گیریم:می بینیم که دو تا سرویس HttpClient و MessageService به سرویس مون inject شدند.برای سرویس MessageService یک mock باید بسازیمبرای سرویس HttpClient نیاز به import کردن ماژولی داریم که Angular برای تست در اختیارمون قرار داده. اسمش هست HttpClientTestingModuleنکته دیگه کنترل ریکوئست های http حین تست هست. Angular برای در اختیار داشتن این کنترل، ابزار دیگه ای در اختیارمون قرار داده بنام HttpTestingController . در تست پیش رو با نحوه کار کردش آشنا میشیم. آخرین نکته تابع خاصی هست بنام TestBed.inject که در نسخه های قدیمی تر بنام TestBed.get در اختیارمون قرار میگیره. این تابع دسترسی ویژه ای به dependency injection registry داره و می تونه instance کلاس های مختلف رو به ما برگردونه.با توجه به نکات بالا، بریم شرایط اولیه قبل از هر تست رو برای HeroService مهیا کنیم:یک mock برای MessageService ساختیمماژول HttpClientTestingModule رو import کردیم.یک instance از httpTestingController برای کنترل request ها آماده کردیم.یک instance از سرویس HeroService برای بررسی عملکردش آماده کردیم.بریم ببینیم این نکته ها چطوری در تست ها استفاده میشن.سرویس HeroService یک تابع داره بنام getHeroمیخوایم عملکرد http request رو در این تابع بررسی کنیم و ببینیم به آدرس درستی درخواست میده یا خیر. فرض میکنیم آدرس API های ما api/heroes هست. یعنی اگر شناسه ۴ رو به تابع getHero پاس بدیم، آدرس نهایی باید باشه api/heroes/4 .در خط اول سرویس ما درخواستش رو به سرویس تست http ، که بجای HttpClient واقعی داخل فضای ماژول و از طریق ماژول httpClientTestingModule وارد کردیم، میفرسته.در خط دوم انتظارمون رو توسط تابع expectOne در httpTestinController تعریف میکنیم. انتظار داریم یک درخواست به آدرس api/heroes/4 فرستاده بشه.در خط سوم به صورت تستی درخواست رو اجرا و جواب تستی رو برمیگردونیم.دقت کنید که این همه شرایط رو به صورت تستی چیدیم برای همین خط سوم. یعنی تا زمانی که req.flush رو صدا نکنیم هیچ اتفاقی نمی افته.با اجرای مجدد تست مون، می بینیم که تست مون درست اتفاق می افته و پاس میشه.یک نکته دیگه باقی مونده هنوز. آیا تست مون واقعا در همه شرایط درست جواب میده؟وقتی خط اول رو حذف کنیم تست به خطا میخوره، پس واقعا ارسال درخواست رو بررسی میکنه.وقتی دو بار درخواست ارسال ریکوئست رو بفرستیم ، به خطا میخوره چون انتطار یک بار درخواست به آدرس api/heroes/4 رو داریم.مشکل اینجاست:وقتی یک بار با شناسه ۳ و یک بار با شناسه ۴ درخواست بدیم به سرویس، تست مون پاس میشه!! برای اینکه مطمئن بشیم درخواست های اشتباه دیگه ای ارسال نمیشه، کنترلر httpTestingController امکانش رو برامون فراهم کرده و بنابر این خط چهارم و آخر رو به تست اضافه میکنیم:و می بینیم که درخواست مون fail میشه.تابع verify به ما اطمینان میده که هیچ ریکوئست دیگه ای که به تست مربوط نیست، درخواست نشده.در بخش چهارم میخوایم با trigger کردن event ها و تست اجزای تمپلت آشنا بشیم.برای مشاهده پست های بیشتر و ارتباط با من از طریق لینکدین اینجا کلیک کنید.صفحه لینکدین من : https://www.linkedin.com/in/rohamtehraniچند تا مقاله دیگه رو هم لینک میدم که اگر دوست داشتید مطالعه کنید:کلاس های standalone در Angularآشنایی با انگولار سیگنال ، فیچر جذاب نسخه ۱۶آشنایی با کتابخانه RxJSانواع Subject در RxJSتشخیص آنلاین/آفلاین بودن اپلیکیشن در انگولارموفق باشید :)