SlideShare a Scribd company logo
MARBLE TESTING RXJS STREAMS
USING JASMINE-MARBLES
A B O U T M E
{
"name": "Ilia Idakiev",
"experience": [
“Google Developer Expert (GDE)“,
"Developer & Co-founder @ HILLGRAND",
"Lecturer in 'Advanced JS' @ Sofia University",
"Contractor / Consultant",
"Public / Private Courses”
],
"involvedIn": [
"Angular Sofia", "SofiaJS / BeerJS",
]
}
!
MARBLE TESTING RXJS STREAMS
REACTIVE EXTENSIONS FOR JAVASCRIPT
▸ RxJS is a library for reactive programming using
Observables, to make it easier to compose
asynchronous or callback-based code.
BUILDING REUSABLE CUSTOM ELEMENTS USING ANGULAR
MARBLE TESTING RXJS STREAMS
WHAT ARE MARBLE DIAGRAMS?
▸ Marble diagrams is a way of visually representing reactive (asynchronous)
data streams.
▸ In a marble diagram, the X axis (left to right) represents time.
▸ The bottom-most line always represents the output of the sequence. That is,
what your code will see if you subscribe to the operator in question.
FILTER OPERATOR
Allow only values that match the filter predicate to continue flowing through the stream
DEBOUNCE TIME OPERATOR
Discard emitted values that take less than the specified time between output
WHERE DO WE USE RXJS
MARBLE TESTING RXJS STREAMS
WHERE DO WE USE RXJS
▸ Angular (it users RxJS internally)
▸ NGRX - a framework for building reactive applications in Angular.
▸ Store - RxJS powered state management for Angular apps, inspired by
Redux.
▸ Effects - Side effect model for @ngrx/store.
Marble Testing RxJS streams
JASMINE-MARBLES
MARBLE TESTING RXJS STREAMS
ASCII MARBLE DIAGRAMS SYNTAX
▸ “ ” - whitespace: horizontal whitespace is ignored, and can be used to help vertically align multiple
marble diagrams.
▸ “-” - frame: 1 "frame" of virtual time passing.
▸ [0-9]+[ms|s|m] - time progression: the time progression syntax lets you progress virtual time by a
specific amount. It's a number, followed by a time unit of milliseconds (ms), seconds (s) or minutes
(m).
▸ ‘|’ - complete: The successful completion of an observable.
▸ ‘#’ - error: An error terminating the observable. This is the observable producer signaling.
▸ [a-z0-9] - any alphanumeric character: Represents a value being emitted by the producer signaling.
Marble Testing RxJS streams
MARBLE TESTING RXJS STREAMS
RXJS SCHEDULERS
▸ QueueScheduler - Executes task synchronously but waits for current task to be
finished.
▸ AsapScheduler - Schedules on the micro task queue.
▸ AsyncScheduler - Schedules on the macro task queue.
▸ AnimationFrameScheduler - Relies on ‘requestAnimationFrame’.
▸ VirtualTimeScheduler - Will execute everything synchronous ordered by delay
and mainly used in testing
MARBLE TESTING RXJS STREAMS
JASMINE-MARBLES TEST SCHEDULER
▸ We can test our asynchronous RxJS code synchronously and deterministically
by virtualizing time using the TestScheduler
▸ At this time the TestScheduler can only be used to test code that uses timers,
like delay/debounceTime/etc (i.e. it uses AsyncScheduler with delays > 1). If
the code consumes a Promise or does scheduling with AsapScheduler/
AnimationFrameScheduler/etc it cannot be reliably tested with TestScheduler,
but instead should be tested more traditionally.
Marble Testing RxJS streams
MARBLE TESTING RXJS STREAMS
TEST SCHEDULER API
▸ testScheduler.run(callback) - callback is being executed, any operator that uses timers/AsyncScheduler (like
delay, debounceTime, etc) will **automatically** use the TestScheduler instead, so that we have "virtual time”.
▸ Helpers:
▸ hot - create hot observable
▸ cold - create cold observable
▸ expectObservable(…).toBe(…) - schedules an assertion for when the TestScheduler flushes.
▸ expectSubscriptions(…).toBe(…) -  schedules an assertion for when the TestScheduler flushes but for
subscriptions on hot/cold observables.
▸ flush() - immediately starts virtual time. Not often used since run() will automatically flush for you when your
callback returns, but in some cases you may wish to flush more than once or otherwise have more control.
MARBLE TESTING RXJS STREAMS
ASCII MARBLE SUBSCRIPTION DIAGRAMS SYNTAX
▸ “^” - subscription point: shows the point in time at which a subscription
happen..
▸ “!” - unsubscription point: shows the point in time at which a subscription is
unsubscribed.
▸ [0-9]+[ms|s|m] - time progression: the time progression syntax lets you
progress virtual time by a specific amount. It's a number, followed by a time
unit of milliseconds (ms), seconds (s) or minutes (m).
▸ ‘-’ - time: 1 frame time passing.
DEMOCREATING OBSERVABLES
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch(‘https://jsonplaceholder.typicode.com/users');.pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch(‘https://jsonplaceholder.typicode.com/users');.pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of, throwError } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
import { of, throwError } from 'rxjs';
import { fromFetch } from 'rxjs/fetch';
import { switchMap } from 'rxjs/operators';
export class UserService {
login = (data: any) => {
return of({ firstName: 'Ivan', lastName: 'Ivanov' })
};
loadUsers = () => {
return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => {
if (data.ok) { return data.json(); }
return throwError('Could not fetch!');
}))
};
};
DEMOCREATING SUBJECTS
import { Subject } from "rxjs";
import { filter, map } from "rxjs/operators";
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from "rxjs/operators";
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from "rxjs/operators";
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from "rxjs/operators";
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from "rxjs/operators";
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from "rxjs/operators";
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
import { Subject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
export class MessageBus {
_mbus: Subject<{ type: string, data: any }>;
constructor() {
this._mbus = new Subject<{ type: string, data: any }>();
this._mbus.subscribe(console.log);
}
listen(type: string) {
return this._mbus.pipe(filter(m => m.type === type), map(m => m.data));
}
send(type: string, data: any) {
this._mbus.next({ type, data });
}
};
DEMOUSING THE MESSAGE BUS
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.render);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.render);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.render);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.render);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.render);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.render);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.changeHandler);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.changeHandler);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.changeHandler);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
export class App extends HTMLElement {
...
constructor(private messageBus: MessageBus) {
super();
...
const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user));
const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users));
const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading));
merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe(
startWith(null), takeUntil(this.isAlive$)
).subscribe(this.changeHandler);
}
loginHandler = () => {
this.messageBus.send('[AUTH] Login', this.formData);
}
disconnectedCallback() {
this.isAlive$.next();
this.isAlive$.complete();
}
}
customElements.define('hg-app', App);
DEMODEALING WITH SIDE EFFECTS
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService, private scheduler?: Scheduler) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
}
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor() { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
}
import { MessageBus } from './message-bus';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
}
import { MessageBus } from './message-bus';
import { UserService } from ‘./user-service';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000, this.scheduler),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { merge, Observable } from 'rxjs';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { merge, Observable } from 'rxjs';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = ...
loadUsers$ = this.messageBus.listen('[USERS] Load Users').pipe(
switchMap(() => this.userService.loadUsers().pipe(
switchMap(users => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[USERS] Load Users Success', data: users }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[USERS] Load Users Failed', data: error },
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { merge, Observable } from 'rxjs';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = ...
loadUsers$ = this.messageBus.listen('[USERS] Load Users').pipe(
switchMap(() => this.userService.loadUsers().pipe(
switchMap(users => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[USERS] Load Users Success', data: users }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[USERS] Load Users Failed', data: error },
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
connect = () => {
merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus);
}
}
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { merge, Observable } from 'rxjs';
import { switchMap, delay, startWith, catchError } from 'rxjs/operators';
export class AppEffects {
login$ = this.messageBus.listen('[AUTH] Login').pipe(
switchMap(data => this.userService.login(data).pipe(
delay(2000),
switchMap((user) => [
{ type: '[AUTH] Login Success', data: user },
{ type: '[USERS] Load Users', data: null }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[AUTH] Login Failed', data: error }
])
))
);
loadUsers$ = this.messageBus.listen('[USERS] Load Users').pipe(
switchMap(() => this.userService.loadUsers().pipe(
switchMap(users => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[USERS] Load Users Success', data: users }
]),
startWith({ type: '[GLOBAL] Set Loader', data: true }),
catchError(error => [
{ type: '[GLOBAL] Set Loader', data: false },
{ type: '[USERS] Load Users Failed', data: error },
])
))
);
constructor(private messageBus: MessageBus, private userService: UserService) { }
DEMOBOOTSTRAP
import { App } from './app';
import { MessageBus } from './message-bus';
import { UserService } from './user-service';
import { AppEffects } from './app-effects';
(function bootstrap() {
const messageBus = new MessageBus();
const userService = new UserService();
const appEffects = new AppEffects(messageBus, userService);
appEffects.connect();
const app = new App(messageBus);
document.body.appendChild(app);
})();
DEMOEFFECT SPECS
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
describe('App Effects Testing', () => {
let effects: AppEffects;
let messageBus: MessageBus;
let actions$: any;
let scheduler: TestScheduler;
let userService: UserService;
beforeEach(() => {
messageBus = new MessageBus();
messageBus._mbus = defer(() => actions$) as any;
userService = new UserService();
scheduler = getTestScheduler();
effects = new AppEffects(messageBus, userService);
effects.connect();
});
it('should test login success', () => {
scheduler.run(({ cold, expectObservable, flush }) => {
const spy = spyOn(userService, 'login').and.callThrough();
actions$ = cold('--a', {
a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } }
});
expectObservable(effects.login$).toBe('--a 1999ms (bc)', {
a: { type: '[GLOBAL] Set Loader', data: true },
b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } },
c: { type: '[USERS] Load Users', data: null }
});
flush();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' });
});
});
DEMOALL SPECS
MARBLE TESTING RXJS STREAMS
CONNECT
GitHub > https://github.com/iliaidakiev (/slides/ - list of future and past events)
Twitter > @ilia_idakiev
THANK YOU!
Ad

More Related Content

What's hot (20)

Intro to RxJava/RxAndroid - GDG Munich Android
Intro to RxJava/RxAndroid - GDG Munich AndroidIntro to RxJava/RxAndroid - GDG Munich Android
Intro to RxJava/RxAndroid - GDG Munich Android
Egor Andreevich
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
Vadym Khondar
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
Fabio Collini
 
Connect S3 with Kafka using Akka Streams
Connect S3 with Kafka using Akka Streams Connect S3 with Kafka using Akka Streams
Connect S3 with Kafka using Akka Streams
Seiya Mizuno
 
Introduction to rx java for android
Introduction to rx java for androidIntroduction to rx java for android
Introduction to rx java for android
Esa Firman
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
Mark
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
Tomáš Kypta
 
Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...
Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...
Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...
Codemotion
 
Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?
Andrzej Ludwikowski
 
SF Scala meet up, lighting talk: SPA -- Scala JDBC wrapper
SF Scala meet up, lighting talk: SPA -- Scala JDBC wrapperSF Scala meet up, lighting talk: SPA -- Scala JDBC wrapper
SF Scala meet up, lighting talk: SPA -- Scala JDBC wrapper
Chester Chen
 
Data in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data EfficientlyData in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data Efficiently
Martin Zapletal
 
Ngrx slides
Ngrx slidesNgrx slides
Ngrx slides
Christoffer Noring
 
Meetup spark structured streaming
Meetup spark structured streamingMeetup spark structured streaming
Meetup spark structured streaming
José Carlos García Serrano
 
Harnessing the Power of Java 8 Streams
Harnessing the Power of Java 8 Streams Harnessing the Power of Java 8 Streams
Harnessing the Power of Java 8 Streams
IndicThreads
 
Functional Programming Past Present Future
Functional Programming Past Present FutureFunctional Programming Past Present Future
Functional Programming Past Present Future
IndicThreads
 
JavaScript Functions
JavaScript Functions JavaScript Functions
JavaScript Functions
Reem Alattas
 
JavaScript 2016 for C# Developers
JavaScript 2016 for C# DevelopersJavaScript 2016 for C# Developers
JavaScript 2016 for C# Developers
Rick Beerendonk
 
Using akka streams to access s3 objects
Using akka streams to access s3 objectsUsing akka streams to access s3 objects
Using akka streams to access s3 objects
Mikhail Girkin
 
Java script advance-auroskills (2)
Java script advance-auroskills (2)Java script advance-auroskills (2)
Java script advance-auroskills (2)
BoneyGawande
 
AJUG April 2011 Cascading example
AJUG April 2011 Cascading exampleAJUG April 2011 Cascading example
AJUG April 2011 Cascading example
Christopher Curtin
 
Intro to RxJava/RxAndroid - GDG Munich Android
Intro to RxJava/RxAndroid - GDG Munich AndroidIntro to RxJava/RxAndroid - GDG Munich Android
Intro to RxJava/RxAndroid - GDG Munich Android
Egor Andreevich
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
Vadym Khondar
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
Fabio Collini
 
Connect S3 with Kafka using Akka Streams
Connect S3 with Kafka using Akka Streams Connect S3 with Kafka using Akka Streams
Connect S3 with Kafka using Akka Streams
Seiya Mizuno
 
Introduction to rx java for android
Introduction to rx java for androidIntroduction to rx java for android
Introduction to rx java for android
Esa Firman
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
Mark
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
Tomáš Kypta
 
Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...
Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...
Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong? - Codemo...
Codemotion
 
Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?
Andrzej Ludwikowski
 
SF Scala meet up, lighting talk: SPA -- Scala JDBC wrapper
SF Scala meet up, lighting talk: SPA -- Scala JDBC wrapperSF Scala meet up, lighting talk: SPA -- Scala JDBC wrapper
SF Scala meet up, lighting talk: SPA -- Scala JDBC wrapper
Chester Chen
 
Data in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data EfficientlyData in Motion: Streaming Static Data Efficiently
Data in Motion: Streaming Static Data Efficiently
Martin Zapletal
 
Harnessing the Power of Java 8 Streams
Harnessing the Power of Java 8 Streams Harnessing the Power of Java 8 Streams
Harnessing the Power of Java 8 Streams
IndicThreads
 
Functional Programming Past Present Future
Functional Programming Past Present FutureFunctional Programming Past Present Future
Functional Programming Past Present Future
IndicThreads
 
JavaScript Functions
JavaScript Functions JavaScript Functions
JavaScript Functions
Reem Alattas
 
JavaScript 2016 for C# Developers
JavaScript 2016 for C# DevelopersJavaScript 2016 for C# Developers
JavaScript 2016 for C# Developers
Rick Beerendonk
 
Using akka streams to access s3 objects
Using akka streams to access s3 objectsUsing akka streams to access s3 objects
Using akka streams to access s3 objects
Mikhail Girkin
 
Java script advance-auroskills (2)
Java script advance-auroskills (2)Java script advance-auroskills (2)
Java script advance-auroskills (2)
BoneyGawande
 
AJUG April 2011 Cascading example
AJUG April 2011 Cascading exampleAJUG April 2011 Cascading example
AJUG April 2011 Cascading example
Christopher Curtin
 

Similar to Marble Testing RxJS streams (20)

Rxjs marble-testing
Rxjs marble-testingRxjs marble-testing
Rxjs marble-testing
Christoffer Noring
 
Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the masters
Ara Pehlivanian
 
Rx java in action
Rx java in actionRx java in action
Rx java in action
Pratama Nur Wijaya
 
[JEEConf-2017] RxJava as a key component in mature Big Data product
[JEEConf-2017] RxJava as a key component in mature Big Data product[JEEConf-2017] RxJava as a key component in mature Big Data product
[JEEConf-2017] RxJava as a key component in mature Big Data product
Igor Lozynskyi
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
Nicolas Quiceno Benavides
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016
Codemotion
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
Francois Zaninotto
 
Developing web-apps like it's 2013
Developing web-apps like it's 2013Developing web-apps like it's 2013
Developing web-apps like it's 2013
Laurent_VB
 
RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]
Igor Lozynskyi
 
TDC2018SP | Trilha Go - Processando analise genetica em background com Go
TDC2018SP | Trilha Go - Processando analise genetica em background com GoTDC2018SP | Trilha Go - Processando analise genetica em background com Go
TDC2018SP | Trilha Go - Processando analise genetica em background com Go
tdc-globalcode
 
MongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-esMongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-es
MongoDB
 
Rxjs swetugg
Rxjs swetuggRxjs swetugg
Rxjs swetugg
Christoffer Noring
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
Ben Lin
 
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
DevClub_lv
 
RxJS Operators - Real World Use Cases - AngularMix
RxJS Operators - Real World Use Cases - AngularMixRxJS Operators - Real World Use Cases - AngularMix
RxJS Operators - Real World Use Cases - AngularMix
Tracy Lee
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
Tracy Lee
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
JSFestUA
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.js
cacois
 
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Codemotion
 
Reactive Programming Patterns with RxSwift
Reactive Programming Patterns with RxSwiftReactive Programming Patterns with RxSwift
Reactive Programming Patterns with RxSwift
Florent Pillet
 
Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the masters
Ara Pehlivanian
 
[JEEConf-2017] RxJava as a key component in mature Big Data product
[JEEConf-2017] RxJava as a key component in mature Big Data product[JEEConf-2017] RxJava as a key component in mature Big Data product
[JEEConf-2017] RxJava as a key component in mature Big Data product
Igor Lozynskyi
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016
Codemotion
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
Francois Zaninotto
 
Developing web-apps like it's 2013
Developing web-apps like it's 2013Developing web-apps like it's 2013
Developing web-apps like it's 2013
Laurent_VB
 
RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]
Igor Lozynskyi
 
TDC2018SP | Trilha Go - Processando analise genetica em background com Go
TDC2018SP | Trilha Go - Processando analise genetica em background com GoTDC2018SP | Trilha Go - Processando analise genetica em background com Go
TDC2018SP | Trilha Go - Processando analise genetica em background com Go
tdc-globalcode
 
MongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-esMongoDB World 2019: Life In Stitch-es
MongoDB World 2019: Life In Stitch-es
MongoDB
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
Ben Lin
 
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019Managing State in React Apps with RxJS by James Wright at FrontCon 2019
Managing State in React Apps with RxJS by James Wright at FrontCon 2019
DevClub_lv
 
RxJS Operators - Real World Use Cases - AngularMix
RxJS Operators - Real World Use Cases - AngularMixRxJS Operators - Real World Use Cases - AngularMix
RxJS Operators - Real World Use Cases - AngularMix
Tracy Lee
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
Tracy Lee
 
JS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless BebopJS Fest 2019. Anjana Vakil. Serverless Bebop
JS Fest 2019. Anjana Vakil. Serverless Bebop
JSFestUA
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.js
cacois
 
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Codemotion
 
Reactive Programming Patterns with RxSwift
Reactive Programming Patterns with RxSwiftReactive Programming Patterns with RxSwift
Reactive Programming Patterns with RxSwift
Florent Pillet
 
Ad

More from Ilia Idakiev (16)

Deep Dive into Zone.JS
Deep Dive into Zone.JSDeep Dive into Zone.JS
Deep Dive into Zone.JS
Ilia Idakiev
 
Creating lightweight JS Apps w/ Web Components and lit-html
Creating lightweight JS Apps w/ Web Components and lit-htmlCreating lightweight JS Apps w/ Web Components and lit-html
Creating lightweight JS Apps w/ Web Components and lit-html
Ilia Idakiev
 
No More Promises! Let's RxJS!
No More Promises! Let's RxJS!No More Promises! Let's RxJS!
No More Promises! Let's RxJS!
Ilia Idakiev
 
Deterministic JavaScript Applications
Deterministic JavaScript ApplicationsDeterministic JavaScript Applications
Deterministic JavaScript Applications
Ilia Idakiev
 
Web Components Everywhere
Web Components EverywhereWeb Components Everywhere
Web Components Everywhere
Ilia Idakiev
 
Building Reusable Custom Elements With Angular
Building Reusable Custom Elements With AngularBuilding Reusable Custom Elements With Angular
Building Reusable Custom Elements With Angular
Ilia Idakiev
 
State management for enterprise angular applications
State management for enterprise angular applicationsState management for enterprise angular applications
State management for enterprise angular applications
Ilia Idakiev
 
Offline progressive web apps with NodeJS and React
Offline progressive web apps with NodeJS and ReactOffline progressive web apps with NodeJS and React
Offline progressive web apps with NodeJS and React
Ilia Idakiev
 
Testing rx js using marbles within angular
Testing rx js using marbles within angularTesting rx js using marbles within angular
Testing rx js using marbles within angular
Ilia Idakiev
 
Predictable reactive state management for enterprise apps using NGRX/platform
Predictable reactive state management for enterprise apps using NGRX/platformPredictable reactive state management for enterprise apps using NGRX/platform
Predictable reactive state management for enterprise apps using NGRX/platform
Ilia Idakiev
 
Angular server side rendering with NodeJS - In Pursuit Of Speed
Angular server side rendering with NodeJS - In Pursuit Of SpeedAngular server side rendering with NodeJS - In Pursuit Of Speed
Angular server side rendering with NodeJS - In Pursuit Of Speed
Ilia Idakiev
 
Angular Offline Progressive Web Apps With NodeJS
Angular Offline Progressive Web Apps With NodeJSAngular Offline Progressive Web Apps With NodeJS
Angular Offline Progressive Web Apps With NodeJS
Ilia Idakiev
 
Introduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web ApplicationsIntroduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web Applications
Ilia Idakiev
 
Reflective injection using TypeScript
Reflective injection using TypeScriptReflective injection using TypeScript
Reflective injection using TypeScript
Ilia Idakiev
 
Zone.js
Zone.jsZone.js
Zone.js
Ilia Idakiev
 
Predictable reactive state management - ngrx
Predictable reactive state management - ngrxPredictable reactive state management - ngrx
Predictable reactive state management - ngrx
Ilia Idakiev
 
Deep Dive into Zone.JS
Deep Dive into Zone.JSDeep Dive into Zone.JS
Deep Dive into Zone.JS
Ilia Idakiev
 
Creating lightweight JS Apps w/ Web Components and lit-html
Creating lightweight JS Apps w/ Web Components and lit-htmlCreating lightweight JS Apps w/ Web Components and lit-html
Creating lightweight JS Apps w/ Web Components and lit-html
Ilia Idakiev
 
No More Promises! Let's RxJS!
No More Promises! Let's RxJS!No More Promises! Let's RxJS!
No More Promises! Let's RxJS!
Ilia Idakiev
 
Deterministic JavaScript Applications
Deterministic JavaScript ApplicationsDeterministic JavaScript Applications
Deterministic JavaScript Applications
Ilia Idakiev
 
Web Components Everywhere
Web Components EverywhereWeb Components Everywhere
Web Components Everywhere
Ilia Idakiev
 
Building Reusable Custom Elements With Angular
Building Reusable Custom Elements With AngularBuilding Reusable Custom Elements With Angular
Building Reusable Custom Elements With Angular
Ilia Idakiev
 
State management for enterprise angular applications
State management for enterprise angular applicationsState management for enterprise angular applications
State management for enterprise angular applications
Ilia Idakiev
 
Offline progressive web apps with NodeJS and React
Offline progressive web apps with NodeJS and ReactOffline progressive web apps with NodeJS and React
Offline progressive web apps with NodeJS and React
Ilia Idakiev
 
Testing rx js using marbles within angular
Testing rx js using marbles within angularTesting rx js using marbles within angular
Testing rx js using marbles within angular
Ilia Idakiev
 
Predictable reactive state management for enterprise apps using NGRX/platform
Predictable reactive state management for enterprise apps using NGRX/platformPredictable reactive state management for enterprise apps using NGRX/platform
Predictable reactive state management for enterprise apps using NGRX/platform
Ilia Idakiev
 
Angular server side rendering with NodeJS - In Pursuit Of Speed
Angular server side rendering with NodeJS - In Pursuit Of SpeedAngular server side rendering with NodeJS - In Pursuit Of Speed
Angular server side rendering with NodeJS - In Pursuit Of Speed
Ilia Idakiev
 
Angular Offline Progressive Web Apps With NodeJS
Angular Offline Progressive Web Apps With NodeJSAngular Offline Progressive Web Apps With NodeJS
Angular Offline Progressive Web Apps With NodeJS
Ilia Idakiev
 
Introduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web ApplicationsIntroduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web Applications
Ilia Idakiev
 
Reflective injection using TypeScript
Reflective injection using TypeScriptReflective injection using TypeScript
Reflective injection using TypeScript
Ilia Idakiev
 
Predictable reactive state management - ngrx
Predictable reactive state management - ngrxPredictable reactive state management - ngrx
Predictable reactive state management - ngrx
Ilia Idakiev
 
Ad

Recently uploaded (20)

How analogue intelligence complements AI
How analogue intelligence complements AIHow analogue intelligence complements AI
How analogue intelligence complements AI
Paul Rowe
 
tecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdftecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdf
fjgm517
 
Heap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and DeletionHeap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and Deletion
Jaydeep Kale
 
TrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business ConsultingTrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business Consulting
Trs Labs
 
Role of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered ManufacturingRole of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered Manufacturing
Andrew Leo
 
What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...
Vishnu Singh Chundawat
 
Semantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AISemantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AI
artmondano
 
AI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global TrendsAI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global Trends
InData Labs
 
IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...
IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...
IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...
organizerofv
 
Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...
Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...
Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...
BookNet Canada
 
Cybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure ADCybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure AD
VICTOR MAESTRE RAMIREZ
 
Electronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploitElectronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploit
niftliyevhuseyn
 
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep DiveDesigning Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
ScyllaDB
 
Quantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur MorganQuantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur Morgan
Arthur Morgan
 
Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)
Ortus Solutions, Corp
 
How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?
Daniel Lehner
 
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In FranceManifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
chb3
 
Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...
Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...
Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...
Impelsys Inc.
 
TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...
TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...
TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...
TrustArc
 
Cyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of securityCyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of security
riccardosl1
 
How analogue intelligence complements AI
How analogue intelligence complements AIHow analogue intelligence complements AI
How analogue intelligence complements AI
Paul Rowe
 
tecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdftecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdf
fjgm517
 
Heap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and DeletionHeap, Types of Heap, Insertion and Deletion
Heap, Types of Heap, Insertion and Deletion
Jaydeep Kale
 
TrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business ConsultingTrsLabs - Fintech Product & Business Consulting
TrsLabs - Fintech Product & Business Consulting
Trs Labs
 
Role of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered ManufacturingRole of Data Annotation Services in AI-Powered Manufacturing
Role of Data Annotation Services in AI-Powered Manufacturing
Andrew Leo
 
What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...What is Model Context Protocol(MCP) - The new technology for communication bw...
What is Model Context Protocol(MCP) - The new technology for communication bw...
Vishnu Singh Chundawat
 
Semantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AISemantic Cultivators : The Critical Future Role to Enable AI
Semantic Cultivators : The Critical Future Role to Enable AI
artmondano
 
AI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global TrendsAI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global Trends
InData Labs
 
IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...
IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...
IEDM 2024 Tutorial2_Advances in CMOS Technologies and Future Directions for C...
organizerofv
 
Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...
Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...
Transcript: #StandardsGoals for 2025: Standards & certification roundup - Tec...
BookNet Canada
 
Cybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure ADCybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure AD
VICTOR MAESTRE RAMIREZ
 
Electronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploitElectronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploit
niftliyevhuseyn
 
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep DiveDesigning Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
ScyllaDB
 
Quantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur MorganQuantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur Morgan
Arthur Morgan
 
Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)Into The Box Conference Keynote Day 1 (ITB2025)
Into The Box Conference Keynote Day 1 (ITB2025)
Ortus Solutions, Corp
 
How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?How Can I use the AI Hype in my Business Context?
How Can I use the AI Hype in my Business Context?
Daniel Lehner
 
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In FranceManifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
chb3
 
Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...
Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...
Enhancing ICU Intelligence: How Our Functional Testing Enabled a Healthcare I...
Impelsys Inc.
 
TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...
TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...
TrustArc Webinar: Consumer Expectations vs Corporate Realities on Data Broker...
TrustArc
 
Cyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of securityCyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of security
riccardosl1
 

Marble Testing RxJS streams

  • 1. MARBLE TESTING RXJS STREAMS USING JASMINE-MARBLES
  • 2. A B O U T M E { "name": "Ilia Idakiev", "experience": [ “Google Developer Expert (GDE)“, "Developer & Co-founder @ HILLGRAND", "Lecturer in 'Advanced JS' @ Sofia University", "Contractor / Consultant", "Public / Private Courses” ], "involvedIn": [ "Angular Sofia", "SofiaJS / BeerJS", ] } !
  • 3. MARBLE TESTING RXJS STREAMS REACTIVE EXTENSIONS FOR JAVASCRIPT ▸ RxJS is a library for reactive programming using Observables, to make it easier to compose asynchronous or callback-based code.
  • 4. BUILDING REUSABLE CUSTOM ELEMENTS USING ANGULAR
  • 5. MARBLE TESTING RXJS STREAMS WHAT ARE MARBLE DIAGRAMS? ▸ Marble diagrams is a way of visually representing reactive (asynchronous) data streams. ▸ In a marble diagram, the X axis (left to right) represents time. ▸ The bottom-most line always represents the output of the sequence. That is, what your code will see if you subscribe to the operator in question.
  • 6. FILTER OPERATOR Allow only values that match the filter predicate to continue flowing through the stream
  • 7. DEBOUNCE TIME OPERATOR Discard emitted values that take less than the specified time between output
  • 8. WHERE DO WE USE RXJS
  • 9. MARBLE TESTING RXJS STREAMS WHERE DO WE USE RXJS ▸ Angular (it users RxJS internally) ▸ NGRX - a framework for building reactive applications in Angular. ▸ Store - RxJS powered state management for Angular apps, inspired by Redux. ▸ Effects - Side effect model for @ngrx/store.
  • 12. MARBLE TESTING RXJS STREAMS ASCII MARBLE DIAGRAMS SYNTAX ▸ “ ” - whitespace: horizontal whitespace is ignored, and can be used to help vertically align multiple marble diagrams. ▸ “-” - frame: 1 "frame" of virtual time passing. ▸ [0-9]+[ms|s|m] - time progression: the time progression syntax lets you progress virtual time by a specific amount. It's a number, followed by a time unit of milliseconds (ms), seconds (s) or minutes (m). ▸ ‘|’ - complete: The successful completion of an observable. ▸ ‘#’ - error: An error terminating the observable. This is the observable producer signaling. ▸ [a-z0-9] - any alphanumeric character: Represents a value being emitted by the producer signaling.
  • 14. MARBLE TESTING RXJS STREAMS RXJS SCHEDULERS ▸ QueueScheduler - Executes task synchronously but waits for current task to be finished. ▸ AsapScheduler - Schedules on the micro task queue. ▸ AsyncScheduler - Schedules on the macro task queue. ▸ AnimationFrameScheduler - Relies on ‘requestAnimationFrame’. ▸ VirtualTimeScheduler - Will execute everything synchronous ordered by delay and mainly used in testing
  • 15. MARBLE TESTING RXJS STREAMS JASMINE-MARBLES TEST SCHEDULER ▸ We can test our asynchronous RxJS code synchronously and deterministically by virtualizing time using the TestScheduler ▸ At this time the TestScheduler can only be used to test code that uses timers, like delay/debounceTime/etc (i.e. it uses AsyncScheduler with delays > 1). If the code consumes a Promise or does scheduling with AsapScheduler/ AnimationFrameScheduler/etc it cannot be reliably tested with TestScheduler, but instead should be tested more traditionally.
  • 17. MARBLE TESTING RXJS STREAMS TEST SCHEDULER API ▸ testScheduler.run(callback) - callback is being executed, any operator that uses timers/AsyncScheduler (like delay, debounceTime, etc) will **automatically** use the TestScheduler instead, so that we have "virtual time”. ▸ Helpers: ▸ hot - create hot observable ▸ cold - create cold observable ▸ expectObservable(…).toBe(…) - schedules an assertion for when the TestScheduler flushes. ▸ expectSubscriptions(…).toBe(…) -  schedules an assertion for when the TestScheduler flushes but for subscriptions on hot/cold observables. ▸ flush() - immediately starts virtual time. Not often used since run() will automatically flush for you when your callback returns, but in some cases you may wish to flush more than once or otherwise have more control.
  • 18. MARBLE TESTING RXJS STREAMS ASCII MARBLE SUBSCRIPTION DIAGRAMS SYNTAX ▸ “^” - subscription point: shows the point in time at which a subscription happen.. ▸ “!” - unsubscription point: shows the point in time at which a subscription is unsubscribed. ▸ [0-9]+[ms|s|m] - time progression: the time progression syntax lets you progress virtual time by a specific amount. It's a number, followed by a time unit of milliseconds (ms), seconds (s) or minutes (m). ▸ ‘-’ - time: 1 frame time passing.
  • 20. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 21. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 22. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 23. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 24. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 25. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch(‘https://jsonplaceholder.typicode.com/users');.pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 26. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch(‘https://jsonplaceholder.typicode.com/users');.pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 27. import { of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 28. import { of, throwError } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 29. import { of, throwError } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; import { switchMap } from 'rxjs/operators'; export class UserService { login = (data: any) => { return of({ firstName: 'Ivan', lastName: 'Ivanov' }) }; loadUsers = () => { return fromFetch('https://jsonplaceholder.typicode.com/users').pipe(switchMap(data => { if (data.ok) { return data.json(); } return throwError('Could not fetch!'); })) }; };
  • 31. import { Subject } from "rxjs"; import { filter, map } from "rxjs/operators"; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 32. import { Subject } from 'rxjs'; import { filter, map } from "rxjs/operators"; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 33. import { Subject } from 'rxjs'; import { filter, map } from "rxjs/operators"; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 34. import { Subject } from 'rxjs'; import { filter, map } from "rxjs/operators"; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 35. import { Subject } from 'rxjs'; import { filter, map } from "rxjs/operators"; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 36. import { Subject } from 'rxjs'; import { filter, map } from "rxjs/operators"; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 37. import { Subject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 38. import { Subject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; export class MessageBus { _mbus: Subject<{ type: string, data: any }>; constructor() { this._mbus = new Subject<{ type: string, data: any }>(); this._mbus.subscribe(console.log); } listen(type: string) { return this._mbus.pipe(filter(m => m.type === type), map(m => m.data)); } send(type: string, data: any) { this._mbus.next({ type, data }); } };
  • 40. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.render); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 41. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.render); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 42. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.render); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 43. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.render); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 44. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.render); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 45. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.render); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 46. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.changeHandler); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 47. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.changeHandler); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 48. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.changeHandler); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 49. export class App extends HTMLElement { ... constructor(private messageBus: MessageBus) { super(); ... const userUpdate$ = messageBus.listen('[AUTH] Login Success').pipe(tap(user => this.loggedUser = user)); const usersUpdate$ = messageBus.listen('[USERS] Load Users Success').pipe(tap(users => this.userList = users)); const isLoadingUpdate$ = messageBus.listen('[GLOBAL] Set Loader').pipe(tap(isLoading => this.isLoading = isLoading)); merge(userUpdate$, usersUpdate$, isLoadingUpdate$).pipe( startWith(null), takeUntil(this.isAlive$) ).subscribe(this.changeHandler); } loginHandler = () => { this.messageBus.send('[AUTH] Login', this.formData); } disconnectedCallback() { this.isAlive$.next(); this.isAlive$.complete(); } } customElements.define('hg-app', App);
  • 51. export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService, private scheduler?: Scheduler) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } } }
  • 52. export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor() { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } } }
  • 53. import { MessageBus } from './message-bus'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } } }
  • 54. import { MessageBus } from './message-bus'; import { UserService } from ‘./user-service'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } } }
  • 55. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 56. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 57. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 58. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 59. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000, this.scheduler), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 60. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 61. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 62. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 63. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay, catchError } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 64. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 65. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { merge, Observable } from 'rxjs'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 66. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { merge, Observable } from 'rxjs'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = ... loadUsers$ = this.messageBus.listen('[USERS] Load Users').pipe( switchMap(() => this.userService.loadUsers().pipe( switchMap(users => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[USERS] Load Users Success', data: users } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[USERS] Load Users Failed', data: error }, ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 67. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { merge, Observable } from 'rxjs'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = ... loadUsers$ = this.messageBus.listen('[USERS] Load Users').pipe( switchMap(() => this.userService.loadUsers().pipe( switchMap(users => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[USERS] Load Users Success', data: users } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[USERS] Load Users Failed', data: error }, ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { } connect = () => { merge(...Object.values(this).filter(val => val instanceof Observable)).subscribe(this.messageBus._mbus); } }
  • 68. import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { merge, Observable } from 'rxjs'; import { switchMap, delay, startWith, catchError } from 'rxjs/operators'; export class AppEffects { login$ = this.messageBus.listen('[AUTH] Login').pipe( switchMap(data => this.userService.login(data).pipe( delay(2000), switchMap((user) => [ { type: '[AUTH] Login Success', data: user }, { type: '[USERS] Load Users', data: null } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[AUTH] Login Failed', data: error } ]) )) ); loadUsers$ = this.messageBus.listen('[USERS] Load Users').pipe( switchMap(() => this.userService.loadUsers().pipe( switchMap(users => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[USERS] Load Users Success', data: users } ]), startWith({ type: '[GLOBAL] Set Loader', data: true }), catchError(error => [ { type: '[GLOBAL] Set Loader', data: false }, { type: '[USERS] Load Users Failed', data: error }, ]) )) ); constructor(private messageBus: MessageBus, private userService: UserService) { }
  • 70. import { App } from './app'; import { MessageBus } from './message-bus'; import { UserService } from './user-service'; import { AppEffects } from './app-effects'; (function bootstrap() { const messageBus = new MessageBus(); const userService = new UserService(); const appEffects = new AppEffects(messageBus, userService); appEffects.connect(); const app = new App(messageBus); document.body.appendChild(app); })();
  • 72. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 73. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 74. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 75. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 76. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 77. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 78. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 79. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 80. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 81. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 82. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 83. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 84. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 85. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 86. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 87. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 88. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 89. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 90. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 91. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 92. describe('App Effects Testing', () => { let effects: AppEffects; let messageBus: MessageBus; let actions$: any; let scheduler: TestScheduler; let userService: UserService; beforeEach(() => { messageBus = new MessageBus(); messageBus._mbus = defer(() => actions$) as any; userService = new UserService(); scheduler = getTestScheduler(); effects = new AppEffects(messageBus, userService); effects.connect(); }); it('should test login success', () => { scheduler.run(({ cold, expectObservable, flush }) => { const spy = spyOn(userService, 'login').and.callThrough(); actions$ = cold('--a', { a: { type: '[AUTH] Login', data: { email: 'aaa', password: 'aaa' } } }); expectObservable(effects.login$).toBe('--a 1999ms (bc)', { a: { type: '[GLOBAL] Set Loader', data: true }, b: { type: '[AUTH] Login Success', data: { firstName: "Ivan", lastName: "Ivanov" } }, c: { type: '[USERS] Load Users', data: null } }); flush(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith({ email: 'aaa', password: 'aaa' }); }); });
  • 94. MARBLE TESTING RXJS STREAMS CONNECT GitHub > https://github.com/iliaidakiev (/slides/ - list of future and past events) Twitter > @ilia_idakiev