[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":,"notification":{"kind":"N","value":"b","hasValue":true}}
{"frame":,"notification":{"kind":"N","value":"c","hasValue":true}}
{"frame":,"notification":{"kind":"C","hasValue":false}} to deep equal
{"frame":30,"notification":{"kind":"N","value":"a","hasValue":true}}
{"frame":,"notification":{"kind":"N","value":"b","hasValue":true}}
{"frame":,"notification":{"kind":"N","value":"c","hasValue":true}}
{"frame":,"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!');
});
[RxJS] Introduction to RxJS Marble Testing的更多相关文章
- [AngularJS + RxJS] Search with RxJS
When doing search function, you always need to consider about the concurrent requests. AEvent ----(6 ...
- [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 ...
- [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 ...
- [RxJS] Split an RxJS observable with window
Mapping the values of an observable to many inner observables is not the only way to create a higher ...
- RxJS v6 学习指南
为什么要使用 RxJS RxJS 是一套处理异步编程的 API,那么我将从异步讲起. 前端编程中的异步有:事件(event).AJAX.动画(animation).定时器(timer). 异步常见的问 ...
- RxJS速成 (上)
What is RxJS? RxJS是ReactiveX编程理念的JavaScript版本.ReactiveX是一种针对异步数据流的编程.简单来说,它将一切数据,包括HTTP请求,DOM事件或者普通数 ...
- RxJS速成 (下)
上一部分: http://www.cnblogs.com/cgzl/p/8641738.html Subject Subject比较特殊, 它即是Observable又是Observer. 作为Obs ...
- RxJS入门
一.RxJS是什么? 官方文档使用了一句话总结RxJS: Think of RxJS as Lodash for events.那么Lodash主要解决了什么问题?Lodash主要集成了一系列关于数组 ...
- RxJS 6有哪些新变化?
我们的前端工程由Angular4升级到Angular6,rxjs也要升级到rxjs6. rxjs6的语法做了很大的改动,幸亏引入了rxjs-compact包,否则升级工作会无法按时完成. 按照官方的 ...
随机推荐
- HDU 5842 Lweb and String
Lweb and String Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)T ...
- C#+AE 用MapControl加载栅格格式文件
需要引入DataSourceRaster命名空间. 具体步骤如下: ①:利用工作控件工厂抽象类定义接口变量,利用工作空间工厂的子类RatserWorkspaceFactory对应的组件类RasterW ...
- <译>Selenium Python Bindings 5 - Waits
如今,大多数的Web应用程序使用AJAX技术.当页面加载到浏览器,页面中的元素也许在不同的时间间隔内加载.这使得元素很难定位,如果在DOM中的元素没有呈现,它将抛出ElementNotVisibleE ...
- 软件测试技术(四)——闰年判断器+ int.Parse错误如何解决
目标程序 本次所测试的目标程序是一个闰年判断器,我们知道,一般情况下年份被4整除就可以了,但是如果遇到百年的时候还需要被400整除,于是有了如下的逻辑判断: bool isRunNian = fals ...
- [原创]谷歌插件 - YE启动助手(YeLauncher)
版本:v1.1 更新时间:2013/11/01 * 代码完善 + 右键关于显示当前版本号,点击并链接到软件帮助页 版本:v1.0 更新时间:2013/10/20 + 插件原型
- python解惑之 __file__ 与argv[0]
在python下,获取当前执行主脚本的方法有两个:sys.argv[0]和__file__. sys.argv[0] 获取主执行文件路径的最佳方法是用sys.argv[0],它可能是一个相对路径,所以 ...
- jedis入门实例
在使用传统的关系数据库,我们都需要依赖一个所谓的实现了jdbc规范的驱动程序来连接数据库,这些驱动程序由各大数据库厂商提供.这些驱动就是jar包,里面就是封装了对数据库的通信协议,我们通过简单的调用就 ...
- HTML 5的消息通知机制
译文来源:http://www.ido321.com/1130.html 原文:HTML 5 Notification 译文:HTML 5 的消息通知机制 译者:dwqs HTML 5 已经被应用到W ...
- bzoj 2095: [Poi2010]Bridges(二分法+混合图的欧拉回路)
[题意] 给定n点m边的无向图,对于边u,v,从u到v边权为c,从v到u的边权为d,问能够经过每条边一次且仅一次,且最大权值最小的欧拉回路. [思路] 二分答案mid,然后切断权值大于mid的边,原图 ...
- CSS定位(CSS定位概述、相对定位、绝对定位、浮动)
CSS 定位属性 CSS 定位属性允许你对元素进行定位. 属性 描述 position 把元素放置到一个静态的.相对的.绝对的.或固定的位置中. top 定义了一个定位元素的上外边距边界与其包含块上边 ...