[RxJS] Introduction to RxJS Marble Testing

Marble testing is an expressive way to test observables by utilizing marble diagrams. This lesson will walk you through the syntax and features, preparing you to start writing marble tests today!

Grep two files from the rxjs

  • https://github.com/ReactiveX/rxjs/blob/master/spec/helpers/marble-testing.ts
  • https://github.com/ReactiveX/rxjs/blob/master/spec/helpers/test-helper.ts
/*
    RxJS marble testing allows for a more natural style of testing observables.
    To get started, you need to include a few helpers libraries, marble-testing.ts and test-helper.ts,
    in your karma.conf or wallaby.js configuration file.
    These files provide helpers for parsing marble diagrams and asserting against the subscription points and result
    of your observables under test. For these examples I will be using Jasmine, but Mocha and Chai works just as well.

    Let‘s get started with the basics of marble testing!

    First, let‘s understand the pieces that make up a valid marble diagram.

    Dash: Indicates a passing of time, you can think of each dash as 10ms when it comes to your tests.
    -----                 <----- 50ms
    Characters: Each character inside the dash indicates an emission.
    -----a-----b-----c    <----- Emit ‘a‘ at 60ms, ‘b‘ at 120ms, ‘c‘ at 180ms
    Pipes |: Pipes indicate the completion point of an observable.
    -----a|               <----- Emit ‘a‘ at 60ms then complete (70ms)
    Parenthesis (): Parenthesis indicate multiple emissions in same time frame, think Observable.of(1,2,3)
    -----(abc|)           <----- Emit ‘a‘‘b‘‘c‘ at 60ms then complete (60ms)
    Caret ^: Indicates the starting point of a subscription, used with expectSubscription assertion.
    ^-------              <----- Subscription point at caret.
    Exclamation Point - !: Indicates the end point of a subscription, also used with expectSubscription assertion.
    ^------!              <----- Subscription starts at caret, ends at exclamation point.
    Pound Sign - #: Indicates an error
    ---a---#              <----- Emit ‘a‘ at 40ms, error at 80ms
    There are also a few methods included to parse marble sequences and transpose values.

    cold(marbles: string, values?: object, error?: any) : Subscription starts when test begins
    cold(--a--b--|, {a: ‘Hello‘, b: ‘World‘})           <----- Emit ‘Hello‘ at 30ms and ‘World‘ at 60ms, complete at 90ms
    hot(marbles: string, values?: object, error?: any) : Behaves like subscription starts at point of caret
    hot(--^--a---b--|, {a: ‘Goodbye‘, b: ‘World‘})      <----- Subscription begins at point of caret
*/

For example we want to test:

const source =       "---a---b---c--|";
const expected =   "---a---b---c--|";

they should be equal.

Here each ‘-‘ means 1. frames.

‘|‘ means completed.

The method we need to use is ‘expectObservable‘ & ‘cold‘:

    it(‘should parse marble diagrams‘, () => {
        const source = cold(‘---a---b---c---|‘);
        const expected =    ‘---a---b---c---|‘;

        expectObservable(source).toBe(expected)
    });

Cold will treat the beginning of the diagram as a subscription point. Now the test passing.

But if we change a little bit:

    it(‘should parse marble diagrams‘, () => {
        const source = cold(‘---a---b---c---|‘);
        const expected =    ‘---a--b---c---|‘;

        expectObservable(source).toBe(expected)
    });

It reports error:

    Expected
    {"frame":30,"notification":{"kind":"N","value":"a","hasValue":true}}
    {"frame":70,"notification":{"kind":"N","value":"b","hasValue":true}}
    {"frame":110,"notification":{"kind":"N","value":"c","hasValue":true}}
    {"frame":150,"notification":{"kind":"C","hasValue":false}}

    to deep equal
    {"frame":30,"notification":{"kind":"N","value":"a","hasValue":true}}
    {"frame":60,"notification":{"kind":"N","value":"b","hasValue":true}}
    {"frame":100,"notification":{"kind":"N","value":"c","hasValue":true}}
    {"frame":140,"notification":{"kind":"C","hasValue":false}}

Test ‘concat‘ opreator:

    it(‘should work with cold observables‘, () => {
        const obs1 = cold(‘-a---b-|‘);
        const obs2 = cold(‘-c---d-|‘);
        const expectedConcatRes = ‘-a---b--c---d-|‘;

        expectObservable(obs1.concat(obs2)).toBe(expectedConcatRes)
    });

Hot‘ observable: Hot will actually let you identify the subscription point yourself:

When testing hot observables you can specify the subscription point using a caret ‘^‘, similar to how you specify subscriptions when utilizing the expectSubscriptions assertion.

    it(‘should work with hot observables‘, () => {
        const obs1 =     hot(‘---a--^--b---|‘);
        const obs2 =  hot(‘-----c---^-----------------d-|‘);
        const expected =           ‘---b--------------d-|‘;

        expectObservable(obs1.concat(obs2)).toBe(expected);
    });

Algin the ^, easy for read

Spread subscription and marble diagram:

    /*
        For certain operators you may want to confirm the point at which
        an observable is subscribed or unsubscribed. Marble testing makes this
        possible by using the expectSubscriptions helper method. The cold and hot
        methods return a subscriptions object, including the frame at which the observable
        would be subscribed and unsubscribed. You can then assert against these
        subscription points by supplying a diagram which indicates the expected behavior.

        ^ - Indicated the subscription point.
        ! - Indicates the point at which the observable was unsubscribed.

        Example subscriptions object: {"subscribedFrame":70,"unsubscribedFrame":140}
    */
    it(‘should identify subscription points‘, () => {
        const obs1 = cold(‘-a---b-|‘);
        const obs2 = cold(‘-c---d-|‘)
        const expected =  ‘-a---b--c---d-|‘;
        const sub1 =      ‘^------!‘
        const sub2 =      ‘-------^------!‘

        expectObservable(obs1.concat(obs2)).toBe(expected);
        expectSubscriptions(obs1.subscriptions).toBe(sub1);
        expectSubscriptions(obs2.subscriptions).toBe(sub2);
    })

Object to map the key and value:

    /*
        Both the hot and cold methods, as well the the toBe method accept an object map as a
        second parameter, indicating the values to output for the appropriate placeholder.
        When the test is executed these values rather than the matching string in the marble diagram.
    */
    it(‘should correctly sub in values‘, () => {
        const values = {a: 3, b: 2};
        const source = cold(  ‘---a---b---|‘, values);
        const expected =      ‘---a---b---|‘;

        expectObservable(source).toBe(expected, values);
    });
    /*
        Multiple emissions occuring in same time frame can be represented by grouping in parenthesis.
        Complete and error symbols can also be included in the same grouping as simulated outputs.
    */
    it(‘should handle emissions in same time frame‘, () => {
        const obs1 = Observable.of(1,2,3,4);
        const expected = ‘(abcd|)‘;

        expectObservable(obs1).toBe(expected, {a: 1, b: 2, c: 3, d: 4});
    });
    /*
        For asynchronous tests RxJS supplies a TestScheduler.
        How it works...
    */
    it(‘should work with asynchronous operators‘, () => {
        const obs1 = Observable
            .interval(10, rxTestScheduler)
            .take(5)
            .filter(v => v % 2 === 0);
        const expected = ‘-a-b-(c|)‘;

        expectObservable(obs1).toBe(expected, {a: 0, b: 2, c: 4});
    });

Error handling:

    /*
        Observables that encounter errors are represented by the pound (#) sign.
        In this case, our observable is retried twice before ultimately emitting an error.
        A third value can be supplied to the toBe method specifying the error to be matched.
    */
    it(‘should handle errors‘, () => {
        const source = Observable.of(1,2,3,4)
            .map(val => {
                if(val > 3){
                    throw ‘Number too high!‘;
                };
                return val;
            })
        .retry(2);

        const expected = ‘(abcabcabc#)‘;

        expectObservable(source).toBe(expected, {a: 1, b: 2, c: 3, d: 4}, ‘Number too high!‘);
    }); 
时间: 2024-10-18 09:08:53

[RxJS] Introduction to RxJS Marble Testing的相关文章

[RxJS] Split an RxJS observable conditionally with windowToggle

There are variants of the window operator that allow you to split RxJS observables in different ways. In this lesson we will explore the windowToggle variant and see one of its use cases in user interfaces. Let's say we want to build a new functional

[RxJS] Split an RxJS Observable into groups with groupBy

groupBy() is another RxJS operator to create higher order observables. In this lesson we will learn how groupBy works for routing source values into different groups according to a calculated key. const numbersObservable = Rx.Observable.interval(500)

RxJS 6有哪些新变化?

我们的前端工程由Angular4升级到Angular6,rxjs也要升级到rxjs6.  rxjs6的语法做了很大的改动,幸亏引入了rxjs-compact包,否则升级工作会无法按时完成. 按照官方的建议,逐步将原rxjs语法改为rxjs6后,要去掉rxjs-compact包. rxjs-compact包无法转换一些语法,我们的工程中没用到这些特性,原rxjs代码可以转为rxjs6. 原文链接:https://segmentfault.com/a/1190000014956260 RxJs 6于

Angular2中的RxJS

RxJS库 RxJS(Reactive Extensions)是个Angular提供的第三方库,实现异步观察模式(asynchronous observable pattern). 启用RxJS操作 RxJS非常大,通常只要我们所需要的特性就好了.Angular在rxjs/Observable模块中提供了简版的Observable,但是它缺乏我们所需要的所有的操作,包括上面提到的map方法. 我们在应用启动时引入所有RxJS操作: import 'rxjs/Rx'; 首先我们观'rxjs/Obs

构建流式应用—RxJS详解

讲之前先说一点哈,插入的图片,不能正常显示,图片一半被遮盖了,如果想看,鼠标右击然后图片地址,图片另存为去看.如果有知道怎么解决的,可以在下面给我评论,我会及时的修改. 好啦,开始: 目录 常规方式实现搜索功能 RxJS · 流 Stream RxJS 实现原理简析 观察者模式 迭代器模式 RxJS 的观察者 + 迭代器模式 RxJS 基础实现 Observable Observer RxJS · Operators Operators ·入门 一系列的 Operators 操作 使用 RxJS

[Angular 2] Managing State in RxJS with StartWith and Scan

The scan operator in RxJS is the main key to managing values and states in your stream. Scan behaves just as a reduce function would, but scan is able to collect values from streams over time. This lesson covers using startWith to set the initial acc

Angular18 RXJS

1 RX 全称是 Reactive Extensions,它是微软开发并维护的基于 Reactive Programming 范式实现的一套工具库集合:RX结合了观察者模式.迭代器模式.函数式编程 RX官方文档:点击前往 2 RXJS RXJS就是RX在JavaScript层面上的实现 RXJS官方文档:点击前往 3 RXJS中解决异步事件管理的一些基本概念 3.1 Observable 可观察对象:表示一个可调用的未来值或者事件的集合 官方文档:点击前往 3.2 Observer 观察者对象:

Rxjs 操作符

1. javascript解决异步编程方案 解决javascript异步编程方案有两种,一种是promise对象形式,还有一种是是Rxjs库形式,Rxjs相对于Promise来说,有好多Promise没有的特性和功能,使用起来更便捷简单: 2. Rxjs 简单介绍 Rxjs 是Reactive Extensions JavaScript 的简写,响应式异步编程:同Promise对象一样,是解决JS异步编程的一种解决方案: 3. Rxjs使用 1. Rxjs是一个库,需要使用npm进行安装: //

Angular2版本更新

2.0.0-beta.0 somnambulant-inauguration (2015-12-15) Enjoy! 2.0.0-alpha.55 (2015-12-15) Bug Fixes router: export ROUTER_LINK_DSL_PROVIDER and hide MockPopStateEvent (fc75220) Features core: enable dev mode by default (3dca9d5) BREAKING CHANGES Before