Angular 6 unit test rxjs 6 operator tap unit test interceptor - rxjs

Since I update my code to the new Rxjs 6, I had to change the interceptor code like this:
auth.interceptor.ts:
...
return next.handle(req).pipe(
tap((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}),
catchError((error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
this.authService.loginRedirect();
}
return observableThrowError(this.handleError(error));
}
})
);
and I'm not able to test the rxjs operators "tap" and "catchError".
Actually i'm only able to test if pipe is called:
it('should intercept and handle request', () => {
const req: any = {
clone: jasmine.createSpy('clone')
};
const next: any = {
handle: () => next,
pipe: () => next
};
spyOn(next, 'handle').and.callThrough();
spyOn(next, 'pipe').and.callThrough();
interceptor.intercept(req, next);
expect(next.handle).toHaveBeenCalled();
expect(next.pipe).toHaveBeenCalled();
expect(req.clone).toHaveBeenCalled();
});
Any help is apreciated on how to spy the rxjs operators

I think the problem is that you shouldn't be testing that operators were called like this at the first place.
Operators in both RxJS 5 and RxJS 6 are just functions that only "make recipe" how the chain is constructed. This means that checking if tap or catchError were called doesn't tell you anything about it's functionality or whether the chain works at all (it might throw an exception on any value and you won't be able to test it).
Since you're using RxJS 6 you should rather test the chain by sending values through. This is well documented and pretty easy to do https://github.com/ReactiveX/rxjs/blob/master/doc/marble-testing.md
In your case you could do something like this:
const testScheduler = new TestScheduler((actual, expected) => {
// some how assert the two objects are equal
// e.g. with chai `expect(actual).deep.equal(expected)`
});
// This test will actually run *synchronously*
testScheduler.run(({ cold }) => {
const next = {
handle: () => cold('-a-b-c--------|'),
};
const output = interceptor.intercept(null, next);
const expected = ' ----------c---|'; // or whatever your interceptor does
expectObservable(output).toBe(expected);
});
I think you'll get the point what this does...

Related

How to rewrite this code using rxjs observables instead async/await?

I need that createUser function returns Observable<UserEntity> but in this function I also have to make 2 queries to DB and check if this user exists. The code below uses async/await and looks pretty good and clean. But the problem is that I use rxjs everywhere in this project and would like to write it somehow using rxjs. Can it be as clean as now but with Observables?
async create(user: CreateUserDTO): Promise<UserEntity> {
const userByEmail = await this.getUserByEmail();
const userByLogin = await this.getUserByLogin();
if (userByLogin || userByEmail)
// retrun error here
return await this.createUser(user);
}
I am using RxJs 6.5
forkJoin will emit result when both async functions getUserByEmail & getUserByLogin complete their execution
If getUserByEmail & getUserByLogin returns Promise,for that using from to convert a promise into an observable
mergeMap to subscribe the inner observable.In our case createUser returns observable
create(user: CreateUserDTO): Observable < UserEntity > {
//If getUserByEmail & getUserByLogin returs Promise
const getUserByEmail$ = from(this.getUserByEmail());
const getUserByLogin$ = from(this.getUserByLogin());
//If Both returns Observable
//const getUserByEmail$ = this.getUserByEmail();
//const getUserByLogin$ = this.getUserByLogin();
return forkJoin({
userByEmail: this.getUserByEmail(),
userByLogin: this.getUserByLogin(),
}).pipe(
tap((res) => {
if (res.userByEmail || res.userByLogin) {
throw 'User exists!';
}
}),
mergeMap(() => {
return from(this.createUser(user));
//If createUser returns Observable,then
//return this.createUser(user);
})
);
}
Assuming that this.getUserByEmail(), this.getUserByLogin() and this.createUser(user) return Promises, the code could look like this
create(user: CreateUserDTO): Observable<UserEntity> {
// with the rxjs from function we turn a Promise into an Observable
const userByEmail$ = from(this.getUserByEmail());
const userByLogin$ = from(this.getUserByLogin());
// with forkjoin we create an Observable which notifies when all the
// Observables which have been passed in as parameters notify
return forkJoin([userByEmail$, userByLogin$]).pipe(
// with concatMap you wait for the upstream Observable (i.e. the
// Observable created by forkJoin) to notify and complete, and then
// you return the next Observable in the chain, which is, in this case,
// the Observable which (when subscribed) creates the user
concatMap(([userByLogin, userByEmail]) =>
if (userByLogin || userByEmail) {
// throw error here
}
return from(this.createUser(user))
})
)
}
Otherwise, if this.getUserByEmail(), this.getUserByLogin() and this.createUser(user) return Observables you do not need to use the from rxjs function and the code would be slightly simpler, like this
create(user: CreateUserDTO): Observable<UserEntity> {
return forkJoin([this.getUserByEmail(), this.getUserByLogin()]).pipe(
concatMap(([userByLogin, userByEmail]) =>
if (userByLogin || userByEmail) {
// throw error here
}
return from(this.createUser(user))
})
)
}

Subscribe two times to one observable

first func:
updateMark(item: MarkDTO) {
this.service
.put(item, this.resource)
.subscribe(() => this.markEdit = null);
}
second func:
put(item: MarkDTO, rcc: string): Observable<MarkDTO> {
const rdto = new MarkRDTO(item);
const url = `${this.getUrl('base')}${rcc}/marks/${rdto.rid}`;
const obs = this.http.put<MarkDTO>(url, rdto, { withCredentials: true })
.pipe(map((r: MarkDTO) => new MarkDTO(r)))
.share();
obs.subscribe(newMark => this.storage.update(newMark, rcc));
return obs;
}
in service i need to update data after request
but also in same time i need to clear current editItem
all of it must be done after i subscribe to one httpRequest
.share() - suport from rxjs-compat package (i want to remove this dep in closest time)
without .share() - work only 1 of 2 steps
current rxjs version is 6.3.3
Help who can...
There is a pipeable share operator, that you would use the same way you use map() (i.e. inside pipe()) and thus doesn't need rxjs-compat.
But you don't need share() here. All you need is the tap() operator:
put(item: MarkDTO, rcc: string): Observable<MarkDTO> {
const rdto = new MarkRDTO(item);
const url = `${this.getUrl('base')}${rcc}/marks/${rdto.rid}`;
return this.http.put<MarkDTO>(url, rdto, { withCredentials: true })
.pipe(
map(r => new MarkDTO(r)),
tap(newMark => this.storage.update(newMark, rcc))
);
}

Testing Observables with jest

How can I test Observables with Jest?
I have an Observable that fires ~every second, and I want to test that the 1st event is correctly fired, before jest times out.
const myObservable = timer(0, 1000); // Example here
it('should fire', () => {
const event = myObservable.subscribe(data => {
expect(data).toBe(0);
});
});
This test passes, but it also passes if I replace with toBe('anything'), so I guess I am doing something wrong.
I tried using expect.assertions(1), but it seems to be only working with Promises.
There are some good examples in the Jest documentation about passing in an argument for the test. This argument can be called to signal a passing test or you can call fail on it to fail the test, or it can timeout and fail.
https://jestjs.io/docs/en/asynchronous.html
https://alligator.io/testing/asynchronous-testing-jest/
Examples
Notice I set the timeout to 1500ms
const myObservable = timer(0, 1000); // Example here
it('should fire', done => {
myObservable.subscribe(data => {
done();
});
}, 1500); // Give 1500ms until it fails
Another way to see if it fails using setTimeout
const myObservable = timer(0, 1000); // Example here
it('should fire', done => {
myObservable.subscribe(data => {
done();
});
// Fail after 1500ms
setTimeout(() => { done.fail(); }, 1500);
}, timeToFail);
My preferred way to test observables, without fake timers and timeouts, is to async, await and use resolves or rejects on an expected converted promise.
it('should do the job', async () => {
await expect(myObservable
.pipe(first())
.toPromise())
.resolves.toEqual(yourExpectation);
});
Update:
In Rxjs 7 and onwards, you can use lastValueFrom or firstValueFrom for the promise convertion
it('should do the job', async () => {
await expect(lastValueFrom(myObservable))
.resolves.toEqual(yourExpectation);
});
test('Test name', (done) => {
service.getAsyncData().subscribe((asyncData)=>{
expect(asyncData).toBeDefined();
done();
})
});
})
the correct way to test any RXJS observable (Jest or no) is to the use the TestScheduler in rxjs/testing:
e.g.:
import { TestScheduler } from 'rxjs/testing';
import { throttleTime } from 'rxjs/operators';
const testScheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal - required
// for TestScheduler assertions to work via your test framework
// e.g. using chai.
expect(actual).deep.equal(expected);
});
// This test runs synchronously.
it('generates the stream correctly', () => {
testScheduler.run((helpers) => {
const { cold, time, expectObservable, expectSubscriptions } = helpers;
const e1 = cold(' -a--b--c---|');
const e1subs = ' ^----------!';
const t = time(' ---| '); // t = 3
const expected = '-a-----c---|';
expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});
From the RXJS marble testing testing docs.
Trying to convert observables, etc. into promises works fine if you have a simple observable. As soon as things become more complicated you are going to struggle without using marble diagrams and the correct testing library.
There are 2 approaches mentioned above
Taking argument done in our test and call it when we have tested.
Convert our observable to promise using firstValueFrom(myObs) or lastValueFrom(myObs). and use async await with them...
If we have multiple observables to test then we have to nest the observables in our test as we can call done() only once. In that case async await approach can come handy.
In this example when we call filter Customer all three observables emits values so we have to test all of them.
it('Filter Customers based on Producers- Valid Case Promise way ',async()=>{
service.filterCustomers('Producer-1');
await expect(firstValueFrom(service.customers$)).resolves.toEqual(['Customer-1']);
await firstValueFrom(service.customers$).then((customers:string[])=>{
expect(customers).toEqual(['Customer-1']);
expect(customers.length).toBe(1);
})
await expect(firstValueFrom(service.products$)).resolves.toEqual([]);
await expect(firstValueFrom(service.types$)).resolves.toEqual([]);
}).
Here's an Angular approach using fakeAsync
Suppose we have a FooService with an Observable closed$ that emit every time we call the dismiss() method of the service.
#Injectable()
export class FooService {
private closeSubject$ = new Subject<void>();
public close$ = this.closeSubject$.asObservable();
public dismiss() {
this.closeSubject$.next();
}
}
Then we can test the close$ emission like this
describe('FooService', () => {
let fooService: FooService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [FooService]
});
fooService= TestBed.inject(FooService);
});
it('should emit a close event upon calling dismiss()', fakeAsync(() => {
const callbackSpy = jest.fn();
fooService.close$.subscribe(() => {
callbackSpy();
});
fooService.dismiss();
tick();
expect(callbackSpy).toHaveBeenCalledTimes(1);
}));
});

Redux Observable: How to return an action from a callback?

I'm using the WebRTC library which has a very specific API. The peerConnection.setRemoteDescription method's 2nd argument is supposed to be a callback for when it finishes setting the remote description:
This is one of my wrapper functions for my WebRTC class:
export function setRemoteSdp(peerConnection, sdp, callback) {
if (!sdp) return;
return peerConnection.setRemoteDescription(
new RTCSessionDescription(sdp),
callback, // <-------------
);
}
And this is a sketch of what I want to do:
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return WebRTC.setRemoteSdp(peerConnection, sdp, () => {
return myReducer.myAction(); // <------ return action as the callback
})
})
};
This doesn't work since I'm not returning an Observable. Is there a way to do this?
P.S. this is the WebRTC API: https://github.com/oney/react-native-webrtc/blob/master/RTCPeerConnection.js#L176
martin's answer is correct about using Observable.create or new Observable--same thing (except it's not clear to me why you need the mergeAll() since the mergeMap will flatten?)
As a bonus, you could also use Observable.bindCallback for this.
// bindCallback is a factory factory, it creates a function that
// when called with any arguments will return an Observable that
// wraps setRemoteSdp, handling the callback portion for you.
// I'm using setRemoteSdp.bind(WebRTC) because I don't know
// if setRemoteSdp requires its calling context to be WebRTC
// so it's "just in case". It might not be needed.
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
setRemoteSdpObservable(peerConnection, sdp)
.subscribe(d => console.log(d));
Usage inside your epic would be something like this
// observables are lazy, so defining this outside of our epic
// is totally cool--it only sets up the factory
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return setRemoteSdpObservable(peerConnection)
.map(result => myReducer.myAction());
})
};
You could use this to create Observable wrappers for all the WebRTC apis.
So the problem is that setRemoteSdp doesn't return an Observable while myReducer.myAction() does and that's the Observable you want to merge?
You can use Observable.create and wrap the WebRTC.setRemoteSdp call:
.mergeMap(action => {
return Observable.create(observer => {
WebRTC.setRemoteSdp(peerConnection, sdp, () => {
observer.next(myReducer.myAction());
observer.complete();
})
});
}
.mergeAll()
The Observable.create returns an Observable that emits another Observable from myReducer.myAction(). Now I have in fact so-called higher-order that I want to flatten using mergeAll() (concatAll would work as well).

How to test redux-thunk middleware async functions?

I'm trying to test my asyn thunk middleware function using mocha, chai and sinon (my first time!).
Please consider my files:
ayncActionCreators.js
export const fetchCurrentUser = () => {
return (dispatch) => {
setTimeout(dispatch, 100);
}
};
ayncActionCreators.spec.js
//...
it('Should work', () => {
const dispatch = sinon.spy();
const action = fetchCurrentUser();
action(dispatch);
expect(dispatch.called).to.be.true;
});
I did not yet implement the fetchCurrentUser function - just assumed it will take some "server" time and then it will call 'dispatch()'.
The spec fails, due to the async flow. If I add a setTimeout of 101 ms before the expect - it passes.
My code will use some DB API that returns promise, so the async function will eventually look like:
//...
return (dispatch) => {
return dbAPI.fetchUser().then(dispatch(....));
}
So I tried to require dbAPI and create a sinon.stub().returns(Promise.resolve()) inside the test and it didn't work as well (I thought that since the stub returns a resolved promise - the async function will act like a synchronous function).
Any ideas how should I test async functions like that?
Thank,
Amit.
Don't mock dispatch with sinon, write your own and call Mocha's done() in that when it's done.
it('Should work', (done) => {
const dispatch = () => {
// Do your tests here
done();
};
const action = fetchCurrentUser();
action(dispatch)
// Also allow quick failures if your promise fails
.catch(done);
})
If you're just wanting to ensure that the dispatch is called, then mocha will time out. The catch on the returned promise from your async action creator allows errors to be shown in the right place and for the test to fail rather than time out.
Well, I think I've found a solution:
Assuming my async function looks like this:
//...
return (dispatch) => {
return dbAPI.fetchUser().then(dispatch(....));
}
Then I can write the spec as follows:
it('Should work', () => {
dbAPI.fetchUser = sinon.stub().returns(Promise.resolve({username: 'John'}));
const dispatch = sinon.spy();
const action = fetchCurrentUser();
action(dispatch).then(() => {
expect(dispatch.called).to.be.true;
});
});
I don't know if this is a workaround or not, but it works. I would appreciate your opinions of a better way of doing this...
Thanks,
Amit.

Resources