Angular: آزمایش موارد async در منطقه fakedAsync VS. ارائه برنامه ریزان سفارشی

بارها از من درباره "منطقه تقلبی" و نحوه استفاده از آن سؤال شده است. به همین دلیل تصمیم گرفتم این مقاله را برای نوشتن مشاهداتم هنگام تست "fakeAsync" ریز دانه بنویسم.

این منطقه بخش مهمی از اکوسیستم Angular است. شاید کسی بخواند که این منطقه فقط نوعی "زمینه اعدام" است. در حقیقت ، Monkeypat عملکردهای جهانی مانند setTimeout یا setInterval را به منظور رهگیری توابع در حال اجرا بعد از تاخیر (setTimeout) یا دوره ای (setInterval) اجرا می کند.

ذکر این نکته حائز اهمیت است که این مقاله نمی تواند نحوه برخورد با هک های setTimeout را نشان دهد. از آنجا که Angular استفاده سنگینی از RxJs می کند که به عملکردهای زمانبندی بومی متکی است (ممکن است تعجب کنید اما واقعیت دارد) ، از منطقه به عنوان ابزاری پیچیده و در عین حال قدرتمند برای ثبت تمام اقدامات ناهمزمان که ممکن است بر وضعیت برنامه تأثیر بگذارد استفاده می کند. زاویه ای آنها را رهگیری می کند تا بداند آیا هنوز اثری در صف وجود دارد یا خیر. بسته به زمان ، صف را تخلیه می کند. به احتمال زیاد ، کارهای تخلیه شده مقادیر متغیرهای مؤلفه را تغییر می دهند. در نتیجه ، قالب دوباره ارائه می شود.

اکنون ، همه چیزهای async چیزی نیست که ما باید نگران آن باشیم. درک آنچه که در زیر هود اتفاق می افتد بسیار خوب است زیرا به نوشتن تست های واحد موثر کمک می کند. علاوه بر این ، توسعه محور تست تأثیر زیادی روی کد منبع دارد ("مبدأ TDD تمایل به انجام تست رگرسیون خودکار قوی بود که از طراحی تکاملی پشتیبانی می کند. در طی راه متوجه می شدند که تمرین کنندگان آن نوشتن تست های نوشتن را برای اولین بار بهبود قابل توجهی در روند طراحی ایجاد می کنند). "مارتین فاولر ، https://martinfowler.com/articles/mocksArentStubs.html ، 09/2017).

در نتیجه همه این تلاشها می توانیم زمان را تغییر دهیم ، همانطور که باید برای تست در یک نقطه خاص از زمان انجام دهیم.

طرح کلی fakeAsync / تیک

اسناد Angular بیان می کنند که fakeAsync (https://angular.io/guide/testing#fake-async) تجربه کدگذاری خطی تری را ارائه می دهد ، زیرا از قول هایی مانند .whenStable () خلاص می شود و سپس (...).

کد موجود در بلوک fakeAsync به شرح زیر است:

تیک (100)؛ // منتظر بمانید تا اولین کار انجام شود
fixture.detectChanges ()؛ // نمایش به روز رسانی با نقل قول
تیک ()؛ // منتظر بمانید تا کار دوم انجام شود
fixture.detectChanges ()؛ // نمایش به روز رسانی با نقل قول

قطعه های زیر بینشی در مورد نحوه عملکرد fakeAsync ارائه می دهد.

setTimeout / setInterval در اینجا مورد استفاده قرار می گیرد زیرا به وضوح نشان می دهد چه موقع توابع در منطقه fakeAsync اجرا می شوند. شما ممکن است انتظار داشته باشید که این تابع "آن" باید بداند چه زمان آزمایش انجام می شود (در یاس که با استدلال انجام شده است: عملکرد) ، اما این بار ما به جای استفاده از هر نوع تماس برگشتی به اصحاب fakeAsync متکی هستیم.

آن ("وظیفه منطقه را به صورت کار تخلیه می کند") ، fakeAsync (() =>
        setTimeout (() =>
            بگذارید i = 0؛
            const handle = setInterval (() =>
                اگر (من ++ === 5)
                    clearInterval (دسته)؛
                }
            } ، 1000)؛
        ، 10000)؛
}))؛

با صدای بلند شکایت می کند زیرا هنوز تعدادی "تایمر" (= setTimeout) در صف وجود دارد:

خطا: 1 تایمر (ها) هنوز در صف است.

بدیهی است که ما باید زمان را تغییر دهیم تا عملکرد تکمیل شده را انجام دهیم. ما با 10 ثانیه پارامترهای "تیک" را ضمیمه می کنیم:

تیک (10000)؛

هیو؟ خطا گیج کننده تر می شود. اکنون ، آزمایش به دلیل "تایمرهای دوره ای" درج شده (= setIntervals) ضعیف است:

خطا: 1 تایمر (های) دوره ای هنوز در صف است.

از آنجا که عملکردی را که باید در هر ثانیه اجرا شود ، رمزگذاری کردیم ، باید با استفاده از کنه دوباره زمان را تغییر دهیم. عملکرد بعد از 5 ثانیه خاتمه می یابد. به همین دلیل باید 5 ثانیه دیگر اضافه کنیم:

تیک (15000)؛

اکنون ، آزمون در حال گذر است. شایان ذکر است که منطقه به طور موازی وظایفی را اجرا می کند. فقط کافی است عملکرد فراخوان شده را با یک تماس دیگر تنظیم کنید.

آن ("وظیفه منطقه را به صورت کار تخلیه می کند") ، fakeAsync (() =>
    setTimeout (() =>
        بگذارید i = 0؛
        const handle = setInterval (() =>
            اگر (++ i === 5)
                clearInterval (دسته)؛
            }
        } ، 1000)؛
        بگذارید j = 0؛
        const handle2 = setInterval (() =>
            اگر (++ j === 3)
                clearInterval (دسته 2)؛
            }
        } ، 1000)؛
    ، 10000)؛
    تیک (15000)؛
}))؛

این آزمایش همچنان در حال گذر است زیرا هر دو آن مجموعه در همان لحظه شروع شده اند. هر دوی آنها با گذشت 15 ثانیه انجام می شوند:

عمل fakeAsync / تیک بزنید

اکنون می دانیم که چگونه fakeAsync / تیک کار می کند. اجازه دهید آن را برای برخی از مطالب با معنی استفاده کنید.

بیایید زمینه پیشنهادی مانند را ایجاد کنیم که این شرایط را برآورده کند:

  • نتیجه را از برخی API (سرویس) می گیرد
  • این ورودی کاربر را به منظور انتظار برای جستجوی واژه جستجو نهایی می اندازد (تعداد درخواستها را کاهش می دهد). DEBOUNCING_VALUE = 300
  • این نتیجه را در UI نشان می دهد و پیام مناسبی را منتشر می کند
  • آزمون واحد به ماهیت ناهمزمان کد احترام می گذارد و رفتار مناسب میدان پیشنهادی مانند را از نظر زمان گذشت آزمایش می کند

ما در پایان با این سناریوهای آزمایش:

توصیف ("در جستجو" ، () =>
    آن ("نتیجه قبلی را پاک می کند") ، fakeAsync (() =>
    }))؛
    آن ("سیگنال شروع را ساطع می کند" ، fakeAsync (() =>
    }))؛
    این ("بازدیدهای احتمالی API را به 1 درخواست در هر میلی ثانیه ثانیه DEBOUNCING_VALUE" ، fakeAsync (() =>
    }))؛
})؛
توصیف ("در موفقیت" ، () =>
    آن ("API گوگل را صدا می کند") ، fakeAsync (() =>
    }))؛
    آن ("سیگنال موفقیت را با تعداد مسابقات مطابقت می دهد") ، fakeAsync (() =>
    }))؛
    آن ("عناوین موجود در قسمت پیشنهادی را نشان می دهد")، fakeAsync (() =>
    }))؛
})؛
توصیف ("روی خطا" ، () =>
    آن ("سیگنال خطا را ساطع می کند" ، fakeAsync (() =>
    }))؛
})؛

در جستجوی "ما" منتظر نتیجه جستجو نیستیم. هنگامی که کاربر یک ورودی (به عنوان مثال "Lon") ارائه می دهد ، گزینه های قبلی پاک می شوند. ما انتظار داریم که گزینه ها خالی باشد. علاوه بر این ، ورودی کاربر باید پرتاب شود ، بیایید بگوییم با ارزش 300 میلی ثانیه. از نظر منطقه ، یک میکروتیک 300 میلی متری به صف رانده می شود.

توجه داشته باشید که برخی جزئیات را برای کوتاه بودن حذف می کنم:

  • تنظیم تست تقریباً مشابه آنچه در اسناد Angular دیده می شود ، است
  • نمونه apiService از طریق فیکسچر.debugElement.injector تزریق می شود (…)
  • SpecUtils وقایع مربوط به کاربر مانند ورودی و تمرکز را برانگیخته است
قبل از هر (() =>
    spyOn (apiService، 'query'). and.returnValue (مشاهده ناشناخته (queryResult))؛
})؛
fit ("نتیجه قبلی را پاک می کند") ، fakeAsync (() =>
    comp.options = ['خالی نیست']؛
    SpecUtils.focusAndInput ("لون" ، فیکسچر ، "ورودی")؛
    تیک (DEBOUNCING_VALUE)؛
    fixture.detectChanges ()؛
    انتظار (comp.options.l طول) .toBe (0 ، `بود [$ {comp.options.join ('،')}]`)؛
}))؛

کد مؤلفه در تلاش برای راضی کردن آزمون:

ngOnInit ()
    this.control.valueChanges.debounceTime (300) .subscribe (مقدار =>
        this.options = []؛
        this.suggest (مقدار)؛
    })؛
}
پیشنهاد (q: رشته) {
    this.googleBooksAPI.query (q). اشتراک (نتیجه => {
// ...
    } ، () =>
// ...
    })؛
}

اجازه دهید کد را بصورت مرحله به مرحله طی کنیم:

ما از روش پرس و جو apiService که می خواهیم در مؤلفه استفاده کنیم جاسوسی می کنیم. queryResult متغیر حاوی داده های مسخره مانند "هملت" ، "مکبث" و "کینگ لیر" است. در ابتدا انتظار داریم گزینه ها خالی باشد اما همانطور که احتمالاً متوجه شده اید که تمام صف fakeAsync با تیک تخلیه می شود (DEBOUNCING_VALUE) و بنابراین این مؤلفه حاوی نتیجه نهایی نوشته های شکسپیر است:

انتظار داشت 3 تا 0 باشد ([هملت ، مکبث ، کینگ لیر] بود).

ما برای درخواست پرس و جو خدمات به تأخیر نیاز داریم تا بتوانیم گذر زمان ناهمزمان از زمان مصرف شده توسط تماس API را تقلید کنیم. بگذارید 5 ثانیه تأخیر اضافه شود (REQUEST_DELAY = 5000) و تیک (5000) را بزنید.

قبل از هر (() =>
    spyOn (apiService، 'query'). and.returnValue (Observable.of (queryResult) .delay (1000))؛
})؛

fit ("نتیجه قبلی را پاک می کند") ، fakeAsync (() =>
    comp.options = ['خالی نیست']؛
    SpecUtils.focusAndInput ("لون" ، فیکسچر ، "ورودی")؛
    تیک (DEBOUNCING_VALUE)؛
    fixture.detectChanges ()؛
    انتظار (comp.options.l طول) .toBe (0 ، `بود [$ {comp.options.join ('،')}]`)؛
    تیک (REQUEST_DELAY)؛
}))؛

به نظر من ، این مثال باید کار کند اما Zone.js ادعا می کند که هنوز کارهایی در صف وجود دارد:

خطا: 1 تایمر (های) دوره ای هنوز در صف است.

در این مرحله ما باید عمیق تر برویم تا کارکردهایی را که گمان می کنیم در منطقه گیر کرده اند ببینیم. تنظیم برخی از نقاط شکست ، راهی است که باید انجام شود:

اشکال زدایی منطقه fakeAsync

سپس ، این را در خط فرمان صادر کنید

_fakeAsyncTestZoneSpec._scheduler._schedulerQueue [0] .args [0] [0]

یا محتوای منطقه مانند این را بررسی کنید:

hmmm ، روش شستشوی AsyncScheduler هنوز در صف است ... چرا؟

نام عملکردی که درج شده است روش شستشوی AsyncScheduler است.

خیط و پیت کردن عمومی (عمل: AsyncAction ): باطل
  const {اقدامات} = این؛
  if (this.active)
    اعمال.پوش (عمل)؛
    برگشت؛
  }
  اجازه دهید خطا: هر؛
  this.active = true؛
  انجام دادن {
    if (خطا = action.execute (action.state ، action.delay)) {
      زنگ تفريح؛
    }
  در حالی که (عمل = اقدامات.شفت ())؛ // صف زمانبندی را خاموش کنید
  this.active = false؛
  اگر (خطا) {
    در حالی که (عمل = اقدامات.شفت ()) {
      action.unsubscribe ()؛
    }
    خطای پرتاب؛
  }
}

حال ممکن است بپرسید چه اشکالی در کد منبع یا خود منطقه وجود ندارد.

مشکل این است که منطقه و کنه های ما از همگام سازی خارج هستند.

منطقه خود زمان فعلی (2017) را دارد اما تیک می خواهد عملکردی را که در 01.01.1970 در نظر گرفته شده است + 300 میلی ثانیه + 5 ثانیه انجام دهد.

مقدار برنامه ریز async تأیید می کند که:

وارد کردن {async asyncScheduler} از 'rxjs / برنامه ریز / async'؛
// این را جایی در داخل "آن" قرار دهید
console.info (AsyncScheduler.now ())؛
// → 1503235213879

AsyncZoneTimeInSyncKeeper برای نجات

یک راه حل احتمالی برای این امر داشتن ابزار همگام سازی مانند این است:

کلاس صادرات AsyncZoneTimeInSyncKeeper
    زمان = 0؛
    سازنده ()
        spyOn (AsyncScheduler ، 'اکنون'). and.callFake (() =>
            / * tslint: غیرفعال کردن-خط بعدی * /
            console.info ('time'، this.time)؛
            این را برگردانید.
        })؛
    }
    تیک (زمان ؟: شماره) {
        if (زمان تایپ کردن! == 'تعریف نشده') {
            this.time + = زمان؛
            تیک (this.time)؛
        } دیگه
            تیک ()؛
        }
    }
}

هر زمان که برنامه نویسی async فراخوانده شود ، زمان فعلی را که با بازگشت فعلی () برگشت می کند ، نگه می دارد. این کار می کند زیرا عملکرد تیک () از همان زمان فعلی استفاده می کند. هر دو ، برنامه ریز و منطقه ، زمان مشترک دارند.

توصیه می کنم timeInSyncKeeper را در مرحله قبل از هر مرحله فوری کنید:

توصیف ("در جستجو" ، () =>
    اجازه دهید زمانInSyncKeeper؛
    قبل از هر (() =>
        timeInSyncKeeper = جدید AsyncZoneTimeInSyncKeeper ()؛
    })؛
})؛

حال ، اجازه دهید نگاهی به استفاده از نگهدار همگام سازی زمان بیندازیم. به خاطر داشته باشید که ما باید برای حل این مسئله زمان بندی اقدام کنیم زیرا قسمت متن رد شده است و درخواست زمان می برد.

توصیف کنید ("در جستجو" ، () => {
    اجازه دهید زمانInSyncKeeper؛
    قبل از هر (() =>
        timeInSyncKeeper = جدید AsyncZoneTimeInSyncKeeper ()؛
        spyOn (apiService ، 'query'). و.returnValue (مشاهده ناشناخته (queryResult) .delay (REQUEST_DELAY))؛
    })؛
    آن ("نتیجه قبلی را پاک می کند") ، fakeAsync (() =>
        comp.options = ['خالی نیست']؛
        SpecUtils.focusAndInput ("لون" ، فیکسچر ، "ورودی")؛
        timeInSyncKeeper.tick (DEBOUNCING_VALUE)؛
        fixture.detectChanges ()؛
        انتظار (comp.options.l طول) .toBe (0 ، `بود [$ {comp.options.join ('،')}]`)؛
        timeInSyncKeeper.tick (REQUEST_DELAY)؛
    }))؛
    // ...
})؛

بیایید این مثال را بصورت خطی طی کنیم:

  1. نمونه نگهدارنده همگام سازی را فوراً کنید
timeInSyncKeeper = جدید AsyncZoneTimeInSyncKeeper ()؛

2. اجازه دهید پس از گذشت REQUEST_DELAY ، متد apiService.query را با پرس و جو نتیجه پاسخ دهد. بیایید بگوییم که روش پرس و جو کند است و پس از REQUEST_DELAY = 5000 میلی ثانیه جواب می دهد.

spyOn (apiService ، 'query'). و.returnValue (مشاهده ناشناخته (queryResult))

3. وانمود کنید یک گزینه suggest غیر خالی ‘در قسمت پیشنهاد وجود دارد

comp.options = ['خالی نیست']؛

4- به قسمت "ورودی" در عنصر بومی فیکسچر بروید و مقدار "Lon" را وارد کنید. این تعامل کاربر را با قسمت ورودی شبیه سازی می کند.

SpecUtils.focusAndInput ("لون" ، فیکسچر ، "ورودی")؛

5. اجازه دهید دوره زمانی DEBOUNCING_VALUE را در منطقه async جعلی (DEBOUNCING_VALUE = 300 میلی ثانیه) عبور دهیم.

timeInSyncKeeper.tick (DEBOUNCING_VALUE)؛

6. تغییرات را تشخیص داده و الگوی HTML را دوباره ارائه دهید.

fixture.detectChanges ()؛

7. آرایه گزینه ها اکنون خالی است!

انتظار (comp.options.l طول) .toBe (0 ، `بود [$ {comp.options.join ('،')}]`)؛

این بدان معنی است که متغیرهای قابل مشاهده قابل استفاده در مؤلفه ها در زمان مناسب اجرا شدند. توجه داشته باشید که تابع debounTime-d اجرا شده است

مقدار =>
    this.options = []؛
    this.onEvent.emit ({سیگنال: SuggestSignal.start})؛
    this.suggest (مقدار)؛
}

با فراخوانی روش پیشنهادی ، وظیفه دیگری را وارد صف کرد:

پیشنهاد (q: رشته) {
    اگر (! q)
        برگشت؛
    }
    this.googleBooksAPI.query (q). اشتراک (نتیجه => {
        اگر (نتیجه) {
            this.options = result.items.map (item => item.volumeInfo)؛
            this.onEvent.emit ({سیگنال: SuggestSignal.success ، totalItems: result.totalItems})؛
        } دیگه
            this.onEvent.emit ({سیگنال: SuggestSignal.success ، totalItems: 0})؛
        }
    } ، () =>
        this.onEvent.emit ({سیگنال: SuggestSignal.error})؛
    })؛
}

فقط جاسوسی را با استفاده از روش پرس و جو API در گوگل به یاد بیاورید که پس از 5 ثانیه پاسخ می دهد.

8. سرانجام ، ما مجدداً باید برای REQUEST_DELAY = 5000 میلی ثانیه تیک بزنیم تا صف منطقه را بشویم. مشاهده ای که در روش پیشنهادی از آن مشترک می شویم برای تکمیل نیاز به REQUEST_DELAY = 5000 دارد.

timeInSyncKeeper.tick (REQUEST_DELAY)؛

fakeAsync ...؟ چرا؟ برنامه ریزان وجود دارد!

کارشناسان ReactiveX ممکن است استدلال کنند که ما می توانیم از برنامه ریز آزمون استفاده کنیم تا مشاهدات قابل آزمایش باشند. این برای برنامه های Angular امکان پذیر است اما دارای برخی از معایب:

  • این امر مستلزم آن است که با ساختار داخلی ناظران ، اپراتورها ، ...
  • اگر برخی برنامه های زشت setTimeout را در برنامه خود داشته باشید چه می کنید؟ آنها توسط برنامه ریزان اداره نمی شوند.
  • مهمترین مورد: من مطمئن هستم که نمی خواهید از برنامه ریز در کل برنامه خود استفاده کنید. شما نمی خواهید کد تولید را با تست های واحد خود مخلوط کنید. شما نمی خواهید کاری از این دست انجام دهید:
const testScheduler؛
if (Environment.test)
    testScheduler = YourTestScheduler جدید ()؛
}
بگذارید قابل مشاهده باشد؛
if (testScheduler)
    قابل مشاهده = مشاهده پذیر. (‘مقدار). تأخیر (1000 ، testScheduler)
} دیگه
    قابل مشاهده = مشاهده پذیر. (‘مقدار). تأخیر (1000)؛
}

این یک راه حل مناسب نیست. به نظر من ، تنها راه حل ممکن "تزریق" برنامه زمانبندی آزمون با ارائه نوع "پروکسی" برای روش های واقعی Rxjs است. نکته دیگر که باید به آن توجه کنیم این است که روش های مهم می توانند روی تست های واحد باقی مانده تأثیر منفی بگذارند. به همین دلیل ما قصد داریم از جاسوسان یاس استفاده کنیم. جاسوسان بعد از هر کاری پاک می شوند.

عملکرد monkeypatchScheduler اجرای اصلی Rxjs را با استفاده از یک جاسوسی پیچیده می کند. جاسوسی استدلالهای روش را می گیرد و testScheduler را در صورت لزوم اضافه می کند.

واردات {IScheduler} از 'rxjs / Scheduler'؛
واردات {قابل مشاهده} از 'rxjs / قابل مشاهده'؛
اعلام var spyOn: عملکرد؛
تابع صادرات monkeypatchScheduler (برنامه ریز: IScheduler) {
    بگذارید ObsableMethods = ['concat'، 'تعویق'، 'خالی'، 'forkJoin'، 'if'، 'interval'، 'ادغام'، 'از'، 'دامنه'، 'پرتاب'،
        'zip']؛
    اجازه دهید operatorMethods = ['buffer'، 'concat'، 'تأخیر'، 'مشخص'، 'do'، 'every'، 'last'، 'ادغام'، 'max'، 'take'،
        'timeInterval'، 'lift'، 'debounTime']؛
    اجازه دهید injectFn = تابع (پایه: هر ، روشها: رشته []) {
        Method.forEach (متد =>
            const orig = base [روش]؛
            if (typof Orig === "عملکرد") {
                spyOn (پایه ، روش) .and.callFake (عملکرد ()
                    let args = Array.prototype.slice.call (آرگومان ها)؛
                    if (args [args.l length - 1] && typeof args [args.l طول - 1] .now === 'عملکرد') {
                        args [args.l length - 1] = برنامه ریز؛
                    } دیگه
                        args.push (برنامه ریز)؛
                    }
                    بازگرداندن orig.apply (این ، استدلال می کند)؛
                })؛
            }
        })؛
    ؛
    injectFn (قابل مشاهده ، قابل مشاهدهMethods)؛
    injectFn (Observable.prototype، operatorMethods)؛
}

از این پس ، testScheduler تمام کارهای داخل Rxjs را انجام می دهد. از setTimeout / setInterval یا هر نوع چیزهای async استفاده نمی کند. دیگر نیازی به fakeAsync نیست.

اکنون به یک برنامه زمانبندی آزمون نیاز داریم که می خواهیم به monkeypatchScheduler منتقل شویم.

این بسیار مانند TestScheduler پیش فرض رفتار می کند اما یک روش پاسخ به تماس برای عملکرد ارائه می دهد. به این ترتیب ، ما می دانیم پس از کدام دوره زمانی ، کدام عمل اجرا شد.

کلاس صادرات SpyingTestScheduler گسترش می یابد VirtualTimeScheduler
    spyFn: (actionName: رشته ، تأخیر: تعداد ، خطا ؟: هر) => نامعتبر.
    سازنده ()
        فوق العاده (VirtualAction ، defaultMaxFrame)؛
    }
    onAction (spyFn: (actionName: رشته ، تأخیر: شماره ، خطا ؟: هر) => باطل) {
        this.spyFn = spyFn؛
    }
    خیط و پیت کردن ()
        اقدامات const، ، maxFrames} = این؛
        let خطا: هر ، عمل: AsyncAction ؛
        در حالی که ((عمل = اقدامات.شفت ()) و& (this.frame = action.delay) <= maxFrames)
            اجازه دهید stateName = this.detectStateName (عمل)؛
            اجازه تأخیر = action.delay؛
            if (خطا = action.execute (action.state ، action.delay)) {
                if (this.spyFn)
                    this.spyFn (nameName ، تأخیر ، خطا)؛
                }
                زنگ تفريح؛
            } دیگه
                if (this.spyFn)
                    this.spyFn (نام دولت ، تأخیر)؛
                }
            }
        }
        اگر (خطا) {
            در حالی که (عمل = اقدامات.شفت ()) {
                action.unsubscribe ()؛
            }
            خطای پرتاب؛
        }
    }
    خصوصی detectStateName (عمل: AsyncAction ): رشته {
        const c = Object.getPrototypeOf (action.state) .constructor؛
        const argsPos = c.toString (). indexOf ('(')؛
        if (argsPos! == -1)
            بازگشت c.toString () substring (9 ، argsPos)؛
        }
        بازگشت تهی؛
    }
}

در آخر ، اجازه دهید نگاهی به کاربرد داشته باشیم. مثال همان تست واحد قبلی است که قبلاً مورد استفاده قرار گرفته است (این ("نتیجه قبلی را پاک می کند") با این تفاوت که ما می خواهیم به جای fakeAsync / تیک از برنامه ریز آزمون استفاده کنیم.

اجازه دهید testScheduler؛
قبل از هر (() =>
    testScheduler = جدید SpyingTestScheduler ()؛
    testScheduler.maxFrames = 1000000؛
    monkeypatchScheduler (testScheduler)؛
    fixture.detectChanges ()؛
})؛
قبل از هر (() =>
    spyOn (apiService، 'query'). and.callFake (() =>
        Return Obsableable.of (queryResult) .delay (REQUEST_DELAY)؛
    })؛
})؛
آن ("نتیجه قبلی را پاک می کند" ، (انجام می شود: عملکرد) =>
    comp.options = ['خالی نیست']؛
    testScheduler.onAction ((ActionName: رشته ، تأخیر: تعداد ، خطا؟ هر)) =>
        if (actionName === 'DebounTimeSubscriber' && تأخیر === DEBOUNCING_VALUE) {
            انتظار (comp.options.l طول) .toBe (0 ، `بود [$ {comp.options.join ('،')}]`)؛
            انجام شده()؛
        }
    })؛
    SpecUtils.focusAndInput ("لندو" ، ثابت ، "ورودی")؛
    fixture.detectChanges ()؛
    testScheduler.flush ()؛
})؛

برنامه زمانبندی آزمون در اولین قبل از هر چیز ایجاد شده و دارای monkeypatched (!) می باشد. در دوم قبل از هر هجایی ، ما برای خدمت به پرس و جو نتیجه نتیجه نتایج پس از REQUEST_DELAY = 5000 میلی ثانیه ، از apiService.query جاسوسی می کنیم.

حال ، بیایید خط را از طریق آن بگذریم:

  1. اول از همه ، توجه داشته باشید كه ما تابع انجام شده را كه لازم است در رابطه با پاسخ به تماس برنامه ريزي آزمون باشد ، اعلام كرديم. این بدان معنی است که ما باید به یاس بگوییم که آزمایش به تنهایی انجام می شود.
آن ("نتیجه قبلی را پاک می کند" ، (انجام می شود: عملکرد) =>

2. باز هم ، ما برخی گزینه های موجود در مؤلفه را تظاهر می کنیم.

comp.options = ['خالی نیست']؛

3. این به توضیحاتی نیاز دارد زیرا به نظر می رسد در نگاه اول کمی دست و پا چلفتی است. ما می خواهیم در انتظار عملی با عنوان "DebounTimeSubscriber" با تأخیر DEBOUNCING_VALUE = 300 میلی ثانیه باشیم. هنگامی که این اتفاق می افتد ، می خواهیم بررسی کنیم که آیا options.l طول 0 است. سپس ، آزمایش تکمیل شده و ما را انجام داده ایم ().

testScheduler.onAction ((ActionName: رشته ، تأخیر: تعداد ، خطا؟ هر)) =>
    if (actionName === 'DebounTimeSubscriber' && تأخیر === DEBOUNCING_VALUE) {
      انتظار (comp.options.l طول) .toBe (0 ، `بود [$ {comp.options.join ('،')}]`)؛
      انجام شده()؛
    }
})؛

می بینید که استفاده از برنامه ریزان آزمون نیاز به دانش خاصی در مورد داخلی های اجرای Rxjs دارد. البته بستگی به این دارد که شما از کدام برنامه ریزآزمایی استفاده می کنید ، اما حتی اگر یک برنامه ریز قدرتمند را به تنهایی اجرا کنید ، لازم است که زمانبندی ها را بفهمید و مقداری از زمان اجرا را برای انعطاف پذیری (در معرض خطر) قرار دهید.

4. باز هم ، کاربر مقدار "Londo" را وارد می کند.

SpecUtils.focusAndInput ("لندو" ، ثابت ، "ورودی")؛

5. دوباره ، تغییرات را تشخیص داده و دوباره الگو را ارائه دهید.

fixture.detectChanges ()؛

6. سرانجام ، تمام اعمال قرار داده شده در صف برنامه ریز را اجرا می کنیم.

testScheduler.flush ()؛

خلاصه

تجهیزات آزمایشگاهی خود Angular نسبت به خود ساخته ها ترجیح می دهند ... تا زمانی که کار کنند. در بعضی موارد ، زن و شوهر fakeAsync / تیک کار نمی کنند اما دلیلی برای ناامید شدن و حذف تست های واحد وجود ندارد. در این موارد یک ابزار همگام سازی خودکار (که در اینجا به عنوان AsyncZoneTimeInSyncKeeper نیز شناخته می شود) یا یک برنامه زمانبندی تست سفارشی (در اینجا همچنین به عنوان SpyingTestScheduler نیز شناخته شده است) راه است.

کد منبع