W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
本指南涉及使用新的 testScheduler.run(callback)
時(shí)大理石圖的用法。如果不使用 run()
幫助器,此處的某些詳細(xì)信息不適用于手動(dòng)使用 TestScheduler 的情況。
通過使用 TestScheduler 虛擬化時(shí)間,我們可以同步和確定性地測(cè)試異步 RxJS 代碼。ASCII 大理石圖為我們提供了一種直觀的方式來(lái)表示 Observable 的行為。我們可以使用它們來(lái)斷言特定的 Observable 的行為符合預(yù)期,以及創(chuàng)建可以用作模擬的冷熱 Observable。
目前,TestScheduler 僅可用于測(cè)試使用計(jì)時(shí)器的代碼,例如 delay / debounceTime / etc(即,它使用 AsyncScheduler 且延遲& 1)。如果代碼消耗 Promise 或使用 AsapScheduler / AnimationFrameScheduler /等進(jìn)行調(diào)度,則無(wú)法使用 TestScheduler 對(duì)其進(jìn)行可靠的測(cè)試,而應(yīng)采用更傳統(tǒng)的方式進(jìn)行測(cè)試。有關(guān)更多詳細(xì)信息,請(qǐng)參見“ 已知問題部分。
import { TestScheduler } from 'rxjs/testing';
const testScheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal
// e.g. using chai.
expect(actual).deep.equal(expected);
});
// This test will actually run *synchronously*
it('generate the stream correctly', () => {
testScheduler.run(helpers => {
const { cold, expectObservable, expectSubscriptions } = helpers;
const e1 = cold('-a--b--c---|');
const subs = '^----------!';
const expected = '-a-----c---|';
expectObservable(e1.pipe(throttleTime(3, testScheduler))).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(subs);
});
});
提供給您的回調(diào)函數(shù) testScheduler.run(callback)
由 helpers
對(duì)象調(diào)用,該對(duì)象包含用于編寫測(cè)試的函數(shù)。
當(dāng)執(zhí)行此回調(diào)中的代碼時(shí),任何使用計(jì)時(shí)器/ AsyncScheduler 的運(yùn)算符(例如,延遲,debounceTime 等)都將自動(dòng)**使用 TestScheduler,以便我們擁有“虛擬時(shí)間”。您不需要像過去一樣將 TestScheduler 傳遞給他們。
testScheduler.run(helpers => {
const { cold, hot, expectObservable, expectSubscriptions, flush } = helpers;
// use them
});
盡管 run()
完全同步執(zhí)行,但回調(diào)函數(shù)內(nèi)部的輔助函數(shù)卻沒有!這些函數(shù)調(diào)度斷言,這些斷言將在回調(diào)完成或顯式調(diào)用時(shí)執(zhí)行 flush()
。警惕 expect
在回調(diào)中調(diào)用同步斷言,例如, 從所選的測(cè)試庫(kù)中調(diào)用。。
hot(marbleDiagram: string, values?: object, error?: any)
-創(chuàng)建一個(gè)“熱”的可觀察對(duì)象(類似于主題),其行為就像測(cè)試開始時(shí)已經(jīng)在“運(yùn)行”。一個(gè)有趣的區(qū)別是,hot
大理石允許^
角色發(fā)出“零幀”位置的信號(hào)。這是開始訂閱要測(cè)試的可觀察對(duì)象的默認(rèn)點(diǎn)(可以配置-參見 expectObservable
下文)。cold(marbleDiagram: string, values?: object, error?: any)
-創(chuàng)建一個(gè)“冷”可觀察的對(duì)象,其可在測(cè)試開始時(shí)開始訂閱。expectObservable(actual: Observable<T>, subscriptionMarbles?: string).toBe(marbleDiagram: string, values?: object, error?: any)
-計(jì)劃何時(shí)刷新TestScheduler 的斷言。給出 subscriptionMarbles
的參數(shù)更改訂閱和退訂的時(shí)間表。如果不提供該 subscriptionMarbles
參數(shù),它將在開始時(shí)進(jìn)行訂閱,并且永遠(yuǎn)不會(huì)退訂。閱讀以下有關(guān)訂閱大理石圖的信息。expectSubscriptions(actualSubscriptionLogs: SubscriptionLog[]).toBe(subscriptionMarbles: string)
-就像 expectObservable
為 testScheduler 刷新的時(shí)間安排斷言一樣。雙方 cold()
并 hot()
返回一個(gè)可觀察與屬性 subscriptions
類型 SubscriptionLog[]
。給 subscriptions
作為參數(shù)傳遞給 expectSubscriptions
斷言它是否匹配 subscriptionsMarbles
在給定的大理石圖 toBe()
。訂閱大理石圖與可觀察大理石圖略有不同。在下面閱讀更多內(nèi)容。flush()
-立即開始虛擬時(shí)間。很少使用,因?yàn)?code>run()它將在回調(diào)返回時(shí)自動(dòng)為您刷新,但是在某些情況下,您可能希望刷新一次以上,否則將獲得更多控制權(quán)。
在 TestScheduler 的上下文中,大理石圖是一個(gè)包含特殊語(yǔ)法的字符串,表示在虛擬時(shí)間內(nèi)發(fā)生的事件。時(shí)間按幀前進(jìn)。任何大理石弦的第一個(gè)字符始終代表零幀或時(shí)間的開始。在testScheduler.run(callback)
frameTimeFactor 的內(nèi)部設(shè)置為 1,這意味著一幀等于一虛擬毫秒。
一幀代表多少個(gè)虛擬毫秒取決于的值 TestScheduler.frameTimeFactor
。由于遺留原因,僅當(dāng)您的回調(diào)中的代碼正在運(yùn)行時(shí),值才 frameTimeFactor
為 1 。外部設(shè)置為 10。在以后的 RxJS 版本中可能會(huì)更改,因此始終為1。testScheduler.run(callback)
重要提示:本語(yǔ)法指南涉及使用new時(shí)大理石圖的用法
testScheduler.run(callback)
。手動(dòng)使用 TestScheduler 時(shí),大理石圖的語(yǔ)義不同,并且不支持某些功能,例如新的時(shí)間進(jìn)度語(yǔ)法。
' '
空白:水平空白將被忽略,可用于幫助垂直對(duì)齊多個(gè)大理石圖。'-'
幀:虛擬時(shí)間傳遞的1個(gè)“幀”(請(qǐng)參見幀的上述說(shuō)明)。[0-9]+[ms|s|m]
時(shí)間進(jìn)度:時(shí)間進(jìn)度語(yǔ)法使您可以將虛擬時(shí)間提前特定的時(shí)間。它是一個(gè)數(shù)字,后跟時(shí)間單位ms
(毫秒),s
(秒)或m
(分鐘),兩者之間沒有任何空格,例如 a 10ms b
。有關(guān)更多詳細(xì)信息,請(qǐng)參見時(shí)間進(jìn)度語(yǔ)法。'|'
complete:成功完成一個(gè)可觀察的對(duì)象。這是可觀察到的生產(chǎn)者信號(hào) complete()
。'#'
錯(cuò)誤:終止可觀察值的錯(cuò)誤。這是可觀察到的生產(chǎn)者信號(hào) error()
。[a-z0-9]
例如'a'
任何字母數(shù)字字符:表示生產(chǎn)者信令發(fā)出的值 next()
。還請(qǐng)考慮您可以將其映射到這樣的對(duì)象或數(shù)組中:const expected = '400ms (a-b|)';
const values = {
a: 'value emitted',
b: 'another value emitter',
};
expectObservable(someStreamForTesting)
.toBe(expected, values);
// This would work also
const expected = '400ms (0-1|)';
const values = [
'value emitted',
'another value emitted',
];
expectObservable(someStreamForTesting)
.toBe(expected, values);
'()'
同步分組:當(dāng)多個(gè)事件需要同步在同一幀中時(shí),使用括號(hào)將這些事件分組。您可以通過這種方式將下一個(gè)值,完成或錯(cuò)誤分組。初始位置(
確定了其值的發(fā)出時(shí)間。雖然一開始可能很不直觀,但是在所有值同步發(fā)出之后,將進(jìn)行一些幀運(yùn)算,這些幀等于組中的 ASCII 字符數(shù),包括括號(hào)在內(nèi)。例如,'(abc)'
將在同一幀中同步發(fā)出 a,b 和 c 的值,然后將虛擬時(shí)間提前 5 幀,'(abc)'.length === 5
。這樣做是因?yàn)樗ǔ?梢詭椭怪睂?duì)齊大理石圖,但這是實(shí)際測(cè)試中的已知痛點(diǎn)。了解有關(guān)已知問題的更多信息。'^'
訂閱點(diǎn):(僅熱觀測(cè)值)顯示測(cè)試的可觀測(cè)物將訂閱到該熱觀測(cè)值的點(diǎn)。這是可觀察到的“零幀”,在之前的每一幀^
都會(huì)為負(fù)。消極的時(shí)間似乎毫無(wú)意義,但實(shí)際上在某些高級(jí)情況下有必要這樣做,通常涉及 ReplaySubjects。
新的時(shí)間進(jìn)度語(yǔ)法從 CSS 持續(xù)時(shí)間語(yǔ)法中獲得啟發(fā)。它是一個(gè)數(shù)字(整數(shù)或浮點(diǎn)數(shù)),后面緊跟一個(gè)單位;ms(毫秒),s(秒),m(分鐘)。例如100ms
,1.4s
,5.25m
。
如果不是圖的第一個(gè)字符,則必須在前后添加空格,以使其與一系列彈珠區(qū)分開來(lái)。例如 a 1ms b
需要空格,因?yàn)?a1msb
將被解釋為['a', '1', 'm', 's', 'b']
這些字符中的每個(gè)字符都是將被原樣next()的值。
注意:您可能需要從要進(jìn)行的時(shí)間中減去 1 毫秒,因?yàn)樽帜笖?shù)字大理石(代表實(shí)際的發(fā)射值)在發(fā)射后本身已經(jīng)提前了 1 個(gè)虛擬幀。這可能是很不直觀和令人沮喪的,但目前確實(shí)是正確的。
const input = ' -a-b-c|';
const expected = '-- 9ms a 9ms b 9ms (c|)';
/*
// Depending on your personal preferences you could also
// use frame dashes to keep vertical aligment with the input
const input = ' -a-b-c|';
const expected = '------- 4ms a 9ms b 9ms (c|)';
// or
const expected = '-----------a 9ms b 9ms (c|)';
*/
const result = cold(input).pipe(
concatMap(d => of(d).pipe(
delay(10)
))
);
expectObservable(result).toBe(expected);
'-'
或'------'
:等效于 never()
,或從不發(fā)出或完成的可觀察物
|`: 相當(dāng)于 `empty()
#`: 相當(dāng)于 `throwError()
'--a--'
:等待 2 個(gè)“幀”的可觀察對(duì)象,發(fā)出值 a
,然后永不完成。
'--a--b--|'`:在第2幀發(fā)射`a`,在第5幀發(fā)射`b`和在第8幀上`complete
'--a--b--#'`:在第2幀發(fā)射`a`,在第5幀發(fā)射`b`和在第8幀上`error
'-a-^-b--|'
:在熱觀測(cè)下,在 -2 幀上發(fā)射 a
,然后在第 2 幀上發(fā)射 b
,在第5幀上,complete
。
'--(abc)-|'`:在第 2 幀上發(fā)出`a`,`b`和`c`,然后在第 8 幀上發(fā)出`complete
'-----(a|)'
:在第5幀發(fā)出a
和complete
。
'a 9ms b 9s c|'
:在第 0 幀發(fā)射 a
,在第 10 幀發(fā)射 b
,在第 10,012 幀發(fā)射 c
,然后在第 10,013 幀發(fā)射complete
。
'--a 2.5m b'
:在第 2 幀發(fā)出 a
,在第 150,003 幀發(fā)出,b
并且永不完成。
該expectSubscriptions
助手允許你斷言一個(gè) cold()
或 hot()
創(chuàng)建可觀測(cè)是訂閱/退訂在正確的時(shí)間點(diǎn)。在 subscriptionMarbles
對(duì)參數(shù) expectObservable
允許您的測(cè)試,以延遲訂制了更高版本的虛擬時(shí)間,和/或即使觀察到被測(cè)試尚未完成退訂。
訂閱大理石語(yǔ)法與常規(guī)大理石語(yǔ)法略有不同。
'-'
時(shí)間:經(jīng)過1幀時(shí)間。[0-9]+[ms|s|m]
時(shí)間進(jìn)度:時(shí)間進(jìn)度語(yǔ)法使您可以將虛擬時(shí)間提前特定的時(shí)間。它是一個(gè)數(shù)字,后跟時(shí)間單位ms
(毫秒),s
(秒)或m
(分鐘),兩者之間沒有任何空格,例如 a 10ms b
。有關(guān)更多詳細(xì)信息,請(qǐng)參見時(shí)間進(jìn)度語(yǔ)法。'^'
訂閱點(diǎn):顯示訂閱發(fā)生的時(shí)間點(diǎn)。'!'
取消訂閱點(diǎn):顯示取消訂閱的時(shí)間點(diǎn)。
訂購(gòu)大理石圖中,最多 應(yīng)有一個(gè)^
點(diǎn),并且最多 應(yīng)有一個(gè)!
點(diǎn)。除此之外,該-
角色是訂閱大理石圖中唯一允許使用的角色。
'-'
或'------'
:從未發(fā)生過訂閱。
'--^--'
:訂閱在經(jīng)過 2 個(gè)“幀”的時(shí)間后發(fā)生,并且該訂閱并未取消訂閱。
'--^--!-'
:在第 2 幀發(fā)生了訂閱,而在第 5 幀未訂閱。
'500ms ^ 1s !'
:在第 500 幀發(fā)生了訂閱,而在第 1,501 幀未訂閱。
給定熱源,測(cè)試多個(gè)在不同時(shí)間訂閱的訂戶:
testScheduler.run(({ hot, expectObservable }) => {
const source = hot('--a--a--a--a--a--a--a--');
const sub1 = ' --^-----------!';
const sub2 = ' ---------^--------!';
const expect1 = ' --a--a--a--a--';
const expect2 = ' -----------a--a--a-';
expectObservable(source, sub1).toBe(expect1);
expectObservable(source, sub2).toBe(expect2);
});
手動(dòng)退訂永遠(yuǎn)無(wú)法完成的來(lái)源:
it('should repeat forever', () => {
const testScheduler = createScheduler();
testScheduler.run(({ expectObservable }) => {
const foreverStream$ = interval(1).pipe(mapTo('a'));
// Omitting this arg may crash the test suite.
const unsub = '------ !';
expectObservable(foreverStream$, unsub).toBe('-aaaaa');
});
});
有時(shí),我們需要在可觀察到的流完成后斷言狀態(tài)的變化-例如當(dāng)副作用 tap
更新變量時(shí)。在使用 TestScheduler進(jìn) 行 Marbles 測(cè)試之外,我們可能會(huì)認(rèn)為這是造成延遲或在聲明之前等待。
例如:
let eventCount = 0;
const s1 = cold('--a--b|', { a: 'x', b: 'y' });
// side effect using 'tap' updates a variable
const result = s1.pipe(tap(() => eventCount++));
expectObservable(result).toBe('--a--b|', ['x', 'y']);
// flush - run 'virtual time' to complete all outstanding hot or cold observables
flush();
expect(eventCount).toBe(2);
在上述情況下,我們需要完成可觀察的流,以便我們可以測(cè)試將變量設(shè)置為正確的值。TestScheduler 在“虛擬時(shí)間”(同步)中運(yùn)行,但是通常不會(huì)運(yùn)行(并完成),直到 testScheduler 回調(diào)返回。flush()方法手動(dòng)觸發(fā)虛擬時(shí)間,以便我們?cè)诳捎^察值完成后測(cè)試局部變量。
如果您有 RxJS代碼使用 AsyncScheduler 以外的其他任何形式的異步調(diào)度,例如 Promises,AsapScheduler 等,則無(wú)法可靠地將大理石圖用于該特定代碼。這是因?yàn)槟切┢渌恼{(diào)度方法不會(huì)被虛擬化,也不會(huì)為 TestScheduler所了解。
解決方案是使用測(cè)試框架的傳統(tǒng)異步測(cè)試方法來(lái)隔離測(cè)試該代碼。具體細(xì)節(jié)取決于您選擇的測(cè)試框架,但這是一個(gè)偽代碼示例:
// Some RxJS code that also consumes a Promise, so TestScheduler won't be able
// to correctly virtualize and the test will always be really async
const myAsyncCode = () => from(Promise.resolve('something'));
it('has async code', done => {
myAsyncCode().subscribe(d => {
assertEqual(d, 'something');
done();
});
});
與此相關(guān)的是,即使使用 AsyncScheduler,您目前也無(wú)法斷言零延遲,例如 delay(0)
說(shuō) setTimeout(work, 0)
。這樣可以安排一個(gè)新的“任務(wù)”(又稱為“宏任務(wù)”),因此它是異步的,但沒有明確的時(shí)間間隔。
testScheduler.run(callback)
TestScheduler 從 v5 開始就存在,但實(shí)際上是旨在由維護(hù)人員測(cè)試 RxJS 本身,而不是用于常規(guī)用戶應(yīng)用程序中。因此,TestScheduler 的某些默認(rèn)行為和功能對(duì)用戶而言效果不佳(或根本不起作用)。在 V6 我們介紹了testScheduler.run(callback)
這使我們能夠提供新的默認(rèn)值,并在非打破方式特征的方法,但它仍然可以使用TestScheduler之外的 testScheduler.run(callback)
。重要的是要注意,如果這樣做,它的行為會(huì)有一些主要差異。
testScheduler.createColdObservable()
而不是cold()
-a 100ms b-|
TestScheduler.frameTimeFactor = 10
`等于1幀,與連字符相同
-`。maxFrames = 750
。750 之后,它們會(huì)被靜默忽略。
盡管此時(shí) testScheduler.run(callback)
尚未正式棄用外部的 TestScheduler ,但不建議使用它,因?yàn)樗赡軙?huì)引起混亂。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: