W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
本指南涉及使用新的 testScheduler.run(callback)
時大理石圖的用法。如果不使用 run()
幫助器,此處的某些詳細信息不適用于手動使用 TestScheduler 的情況。
通過使用 TestScheduler 虛擬化時間,我們可以同步和確定性地測試異步 RxJS 代碼。ASCII 大理石圖為我們提供了一種直觀的方式來表示 Observable 的行為。我們可以使用它們來斷言特定的 Observable 的行為符合預期,以及創(chuàng)建可以用作模擬的冷熱 Observable。
目前,TestScheduler 僅可用于測試使用計時器的代碼,例如 delay / debounceTime / etc(即,它使用 AsyncScheduler 且延遲& 1)。如果代碼消耗 Promise 或使用 AsapScheduler / AnimationFrameScheduler /等進行調(diào)度,則無法使用 TestScheduler 對其進行可靠的測試,而應采用更傳統(tǒng)的方式進行測試。有關(guān)更多詳細信息,請參見“ 已知問題部分。
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
對象調(diào)用,該對象包含用于編寫測試的函數(shù)。
當執(zhí)行此回調(diào)中的代碼時,任何使用計時器/ AsyncScheduler 的運算符(例如,延遲,debounceTime 等)都將自動**使用 TestScheduler,以便我們擁有“虛擬時間”。您不需要像過去一樣將 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)用時執(zhí)行 flush()
。警惕 expect
在回調(diào)中調(diào)用同步斷言,例如, 從所選的測試庫中調(diào)用。。
hot(marbleDiagram: string, values?: object, error?: any)
-創(chuàng)建一個“熱”的可觀察對象(類似于主題),其行為就像測試開始時已經(jīng)在“運行”。一個有趣的區(qū)別是,hot
大理石允許^
角色發(fā)出“零幀”位置的信號。這是開始訂閱要測試的可觀察對象的默認點(可以配置-參見 expectObservable
下文)。cold(marbleDiagram: string, values?: object, error?: any)
-創(chuàng)建一個“冷”可觀察的對象,其可在測試開始時開始訂閱。expectObservable(actual: Observable<T>, subscriptionMarbles?: string).toBe(marbleDiagram: string, values?: object, error?: any)
-計劃何時刷新TestScheduler 的斷言。給出 subscriptionMarbles
的參數(shù)更改訂閱和退訂的時間表。如果不提供該 subscriptionMarbles
參數(shù),它將在開始時進行訂閱,并且永遠不會退訂。閱讀以下有關(guān)訂閱大理石圖的信息。expectSubscriptions(actualSubscriptionLogs: SubscriptionLog[]).toBe(subscriptionMarbles: string)
-就像 expectObservable
為 testScheduler 刷新的時間安排斷言一樣。雙方 cold()
并 hot()
返回一個可觀察與屬性 subscriptions
類型 SubscriptionLog[]
。給 subscriptions
作為參數(shù)傳遞給 expectSubscriptions
斷言它是否匹配 subscriptionsMarbles
在給定的大理石圖 toBe()
。訂閱大理石圖與可觀察大理石圖略有不同。在下面閱讀更多內(nèi)容。flush()
-立即開始虛擬時間。很少使用,因為run()
它將在回調(diào)返回時自動為您刷新,但是在某些情況下,您可能希望刷新一次以上,否則將獲得更多控制權(quán)。
在 TestScheduler 的上下文中,大理石圖是一個包含特殊語法的字符串,表示在虛擬時間內(nèi)發(fā)生的事件。時間按幀前進。任何大理石弦的第一個字符始終代表零幀或時間的開始。在testScheduler.run(callback)
frameTimeFactor 的內(nèi)部設置為 1,這意味著一幀等于一虛擬毫秒。
一幀代表多少個虛擬毫秒取決于的值 TestScheduler.frameTimeFactor
。由于遺留原因,僅當您的回調(diào)中的代碼正在運行時,值才 frameTimeFactor
為 1 。外部設置為 10。在以后的 RxJS 版本中可能會更改,因此始終為1。testScheduler.run(callback)
重要提示:本語法指南涉及使用new時大理石圖的用法
testScheduler.run(callback)
。手動使用 TestScheduler 時,大理石圖的語義不同,并且不支持某些功能,例如新的時間進度語法。
' '
空白:水平空白將被忽略,可用于幫助垂直對齊多個大理石圖。'-'
幀:虛擬時間傳遞的1個“幀”(請參見幀的上述說明)。[0-9]+[ms|s|m]
時間進度:時間進度語法使您可以將虛擬時間提前特定的時間。它是一個數(shù)字,后跟時間單位ms
(毫秒),s
(秒)或m
(分鐘),兩者之間沒有任何空格,例如 a 10ms b
。有關(guān)更多詳細信息,請參見時間進度語法。'|'
complete:成功完成一個可觀察的對象。這是可觀察到的生產(chǎn)者信號 complete()
。'#'
錯誤:終止可觀察值的錯誤。這是可觀察到的生產(chǎn)者信號 error()
。[a-z0-9]
例如'a'
任何字母數(shù)字字符:表示生產(chǎn)者信令發(fā)出的值 next()
。還請考慮您可以將其映射到這樣的對象或數(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);
'()'
同步分組:當多個事件需要同步在同一幀中時,使用括號將這些事件分組。您可以通過這種方式將下一個值,完成或錯誤分組。初始位置(
確定了其值的發(fā)出時間。雖然一開始可能很不直觀,但是在所有值同步發(fā)出之后,將進行一些幀運算,這些幀等于組中的 ASCII 字符數(shù),包括括號在內(nèi)。例如,'(abc)'
將在同一幀中同步發(fā)出 a,b 和 c 的值,然后將虛擬時間提前 5 幀,'(abc)'.length === 5
。這樣做是因為它通常可以幫助您垂直對齊大理石圖,但這是實際測試中的已知痛點。了解有關(guān)已知問題的更多信息。'^'
訂閱點:(僅熱觀測值)顯示測試的可觀測物將訂閱到該熱觀測值的點。這是可觀察到的“零幀”,在之前的每一幀^
都會為負。消極的時間似乎毫無意義,但實際上在某些高級情況下有必要這樣做,通常涉及 ReplaySubjects。
新的時間進度語法從 CSS 持續(xù)時間語法中獲得啟發(fā)。它是一個數(shù)字(整數(shù)或浮點數(shù)),后面緊跟一個單位;ms(毫秒),s(秒),m(分鐘)。例如100ms
,1.4s
,5.25m
。
如果不是圖的第一個字符,則必須在前后添加空格,以使其與一系列彈珠區(qū)分開來。例如 a 1ms b
需要空格,因為 a1msb
將被解釋為['a', '1', 'm', 's', 'b']
這些字符中的每個字符都是將被原樣next()的值。
注意:您可能需要從要進行的時間中減去 1 毫秒,因為字母數(shù)字大理石(代表實際的發(fā)射值)在發(fā)射后本身已經(jīng)提前了 1 個虛擬幀。這可能是很不直觀和令人沮喪的,但目前確實是正確的。
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ā)出或完成的可觀察物
|`: 相當于 `empty()
#`: 相當于 `throwError()
'--a--'
:等待 2 個“幀”的可觀察對象,發(fā)出值 a
,然后永不完成。
'--a--b--|'`:在第2幀發(fā)射`a`,在第5幀發(fā)射`b`和在第8幀上`complete
'--a--b--#'`:在第2幀發(fā)射`a`,在第5幀發(fā)射`b`和在第8幀上`error
'-a-^-b--|'
:在熱觀測下,在 -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
助手允許你斷言一個 cold()
或 hot()
創(chuàng)建可觀測是訂閱/退訂在正確的時間點。在 subscriptionMarbles
對參數(shù) expectObservable
允許您的測試,以延遲訂制了更高版本的虛擬時間,和/或即使觀察到被測試尚未完成退訂。
訂閱大理石語法與常規(guī)大理石語法略有不同。
'-'
時間:經(jīng)過1幀時間。[0-9]+[ms|s|m]
時間進度:時間進度語法使您可以將虛擬時間提前特定的時間。它是一個數(shù)字,后跟時間單位ms
(毫秒),s
(秒)或m
(分鐘),兩者之間沒有任何空格,例如 a 10ms b
。有關(guān)更多詳細信息,請參見時間進度語法。'^'
訂閱點:顯示訂閱發(fā)生的時間點。'!'
取消訂閱點:顯示取消訂閱的時間點。
訂購大理石圖中,最多 應有一個^
點,并且最多 應有一個!
點。除此之外,該-
角色是訂閱大理石圖中唯一允許使用的角色。
'-'
或'------'
:從未發(fā)生過訂閱。
'--^--'
:訂閱在經(jīng)過 2 個“幀”的時間后發(fā)生,并且該訂閱并未取消訂閱。
'--^--!-'
:在第 2 幀發(fā)生了訂閱,而在第 5 幀未訂閱。
'500ms ^ 1s !'
:在第 500 幀發(fā)生了訂閱,而在第 1,501 幀未訂閱。
給定熱源,測試多個在不同時間訂閱的訂戶:
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);
});
手動退訂永遠無法完成的來源:
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');
});
});
有時,我們需要在可觀察到的流完成后斷言狀態(tài)的變化-例如當副作用 tap
更新變量時。在使用 TestScheduler進 行 Marbles 測試之外,我們可能會認為這是造成延遲或在聲明之前等待。
例如:
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);
在上述情況下,我們需要完成可觀察的流,以便我們可以測試將變量設置為正確的值。TestScheduler 在“虛擬時間”(同步)中運行,但是通常不會運行(并完成),直到 testScheduler 回調(diào)返回。flush()方法手動觸發(fā)虛擬時間,以便我們在可觀察值完成后測試局部變量。
如果您有 RxJS代碼使用 AsyncScheduler 以外的其他任何形式的異步調(diào)度,例如 Promises,AsapScheduler 等,則無法可靠地將大理石圖用于該特定代碼。這是因為那些其他的調(diào)度方法不會被虛擬化,也不會為 TestScheduler所了解。
解決方案是使用測試框架的傳統(tǒng)異步測試方法來隔離測試該代碼。具體細節(jié)取決于您選擇的測試框架,但這是一個偽代碼示例:
// 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,您目前也無法斷言零延遲,例如 delay(0)
說 setTimeout(work, 0)
。這樣可以安排一個新的“任務”(又稱為“宏任務”),因此它是異步的,但沒有明確的時間間隔。
testScheduler.run(callback)
TestScheduler 從 v5 開始就存在,但實際上是旨在由維護人員測試 RxJS 本身,而不是用于常規(guī)用戶應用程序中。因此,TestScheduler 的某些默認行為和功能對用戶而言效果不佳(或根本不起作用)。在 V6 我們介紹了testScheduler.run(callback)
這使我們能夠提供新的默認值,并在非打破方式特征的方法,但它仍然可以使用TestScheduler之外的 testScheduler.run(callback)
。重要的是要注意,如果這樣做,它的行為會有一些主要差異。
testScheduler.createColdObservable()
而不是cold()
-a 100ms b-|
TestScheduler.frameTimeFactor = 10
`等于1幀,與連字符相同
-`。maxFrames = 750
。750 之后,它們會被靜默忽略。
盡管此時 testScheduler.run(callback)
尚未正式棄用外部的 TestScheduler ,但不建議使用它,因為它可能會引起混亂。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: