SlideShare ist ein Scribd-Unternehmen logo
1 von 95
Downloaden Sie, um offline zu lesen
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.
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
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
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!

Weitere ähnliche Inhalte

Was ist angesagt?

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 AndroidEgor Andreevich
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every dayVadym 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 ItalyFabio 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 androidEsa Firman
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScriptMark
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on AndroidTomáš 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 wrapperChester 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 EfficientlyMartin 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 FutureIndicThreads
 
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# DevelopersRick 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 objectsMikhail 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 exampleChristopher Curtin
 

Was ist angesagt? (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
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
 
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
 
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
 
Introduction to rx java for android
Introduction to rx java for androidIntroduction to rx java for android
Introduction to rx java for android
 
CoffeeScript
CoffeeScriptCoffeeScript
CoffeeScript
 
Reactive programming on Android
Reactive programming on AndroidReactive programming on Android
Reactive programming on Android
 
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...
 
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?
 
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
 
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
 
Ngrx slides
Ngrx slidesNgrx slides
Ngrx slides
 
Meetup spark structured streaming
Meetup spark structured streamingMeetup spark structured streaming
Meetup spark structured streaming
 
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
 
Functional Programming Past Present Future
Functional Programming Past Present FutureFunctional Programming Past Present Future
Functional Programming Past Present Future
 
JavaScript Functions
JavaScript Functions JavaScript Functions
JavaScript Functions
 
JavaScript 2016 for C# Developers
JavaScript 2016 for C# DevelopersJavaScript 2016 for C# Developers
JavaScript 2016 for C# Developers
 
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
 
Java script advance-auroskills (2)
Java script advance-auroskills (2)Java script advance-auroskills (2)
Java script advance-auroskills (2)
 
AJUG April 2011 Cascading example
AJUG April 2011 Cascading exampleAJUG April 2011 Cascading example
AJUG April 2011 Cascading example
 

Ähnlich wie Marble Testing RxJS streams

Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the mastersAra 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 productIgor 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 2016Codemotion
 
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 jsFrancois 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 2013Laurent_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 Gotdc-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-esMongoDB
 
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 frameworkBen 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 2019DevClub_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 - AngularMixTracy 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 BebopJSFestUA
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jscacois
 
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 RxSwiftFlorent Pillet
 

Ähnlich wie Marble Testing RxJS streams (20)

Rxjs marble-testing
Rxjs marble-testingRxjs marble-testing
Rxjs marble-testing
 
Expert JavaScript tricks of the masters
Expert JavaScript  tricks of the mastersExpert JavaScript  tricks of the masters
Expert JavaScript tricks of the masters
 
Rx java in action
Rx java in actionRx java in action
Rx java in action
 
[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
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
 
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
 
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
 
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
 
RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]RxJava applied [JavaDay Kyiv 2016]
RxJava applied [JavaDay Kyiv 2016]
 
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
 
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
 
Rxjs swetugg
Rxjs swetuggRxjs swetugg
Rxjs swetugg
 
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
 
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
 
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
 
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)
 
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
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.js
 
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...
 
Reactive Programming Patterns with RxSwift
Reactive Programming Patterns with RxSwiftReactive Programming Patterns with RxSwift
Reactive Programming Patterns with RxSwift
 

Mehr von Ilia Idakiev

Deep Dive into Zone.JS
Deep Dive into Zone.JSDeep Dive into Zone.JS
Deep Dive into Zone.JSIlia 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-htmlIlia 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 ApplicationsIlia Idakiev
 
Web Components Everywhere
Web Components EverywhereWeb Components Everywhere
Web Components EverywhereIlia Idakiev
 
Building Reusable Custom Elements With Angular
Building Reusable Custom Elements With AngularBuilding Reusable Custom Elements With Angular
Building Reusable Custom Elements With AngularIlia Idakiev
 
State management for enterprise angular applications
State management for enterprise angular applicationsState management for enterprise angular applications
State management for enterprise angular applicationsIlia 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 ReactIlia 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 angularIlia 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/platformIlia 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 SpeedIlia 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 NodeJSIlia Idakiev
 
Introduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web ApplicationsIntroduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web ApplicationsIlia Idakiev
 
Reflective injection using TypeScript
Reflective injection using TypeScriptReflective injection using TypeScript
Reflective injection using TypeScriptIlia Idakiev
 
Predictable reactive state management - ngrx
Predictable reactive state management - ngrxPredictable reactive state management - ngrx
Predictable reactive state management - ngrxIlia Idakiev
 

Mehr von Ilia Idakiev (16)

Deep Dive into Zone.JS
Deep Dive into Zone.JSDeep Dive into Zone.JS
Deep Dive into Zone.JS
 
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
 
No More Promises! Let's RxJS!
No More Promises! Let's RxJS!No More Promises! Let's RxJS!
No More Promises! Let's RxJS!
 
Deterministic JavaScript Applications
Deterministic JavaScript ApplicationsDeterministic JavaScript Applications
Deterministic JavaScript Applications
 
Web Components Everywhere
Web Components EverywhereWeb Components Everywhere
Web Components Everywhere
 
Building Reusable Custom Elements With Angular
Building Reusable Custom Elements With AngularBuilding Reusable Custom Elements With Angular
Building Reusable Custom Elements With Angular
 
State management for enterprise angular applications
State management for enterprise angular applicationsState management for enterprise angular applications
State management for enterprise angular applications
 
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
 
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
 
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
 
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
 
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
 
Introduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web ApplicationsIntroduction to Offline Progressive Web Applications
Introduction to Offline Progressive Web Applications
 
Reflective injection using TypeScript
Reflective injection using TypeScriptReflective injection using TypeScript
Reflective injection using TypeScript
 
Zone.js
Zone.jsZone.js
Zone.js
 
Predictable reactive state management - ngrx
Predictable reactive state management - ngrxPredictable reactive state management - ngrx
Predictable reactive state management - ngrx
 

Kürzlich hochgeladen

New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 

Kürzlich hochgeladen (20)

New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 

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.
  • 10.
  • 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.
  • 13.
  • 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.
  • 16.
  • 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