Verify observable value from BehaviorSubject in subscription - jasmine

I've got a simple service that queries the web and then based on the return, it emits an event through a BehaviorSubject
export class ProducerService {
static readonly baseUrl = `${environment.apiUri}/producers`
private readonly admin = new BehaviorSubject<boolean>(false)
readonly admin$ = this.admin.asObservable()
constructor(private readonly http: HttpClient) {
}
queryAdmin(): void {
this.http.get<boolean>(`${ProducerService.baseUrl}/admin`)
.subscribe(x => this.admin.next(x))
}
}
Now I'm trying to write a test that verifies if true is passed in that the admin$ variable gets set to true. I tried it like this
it('should emit true when an admin', async(() => {
service.admin$.subscribe(x => expect(x).toBeTrue())
service.queryAdmin()
const req = httpMock.expectOne({url: `${ProducerService.baseUrl}/admin`, method: 'GET'})
req.flush(true)
}))
That fails though saying "Expected false to be true". What am I doing wrong?

The BehaviorSubject is "hot" so it is ready to go when you subscribe to it and it has an initial value of false, then you're asserting false toBeTrue.
Try to filter out the false values using the filter operator of Rxjs.
import { filter } from 'rxjs/operators';
....
it('should emit true when an admin', async((done) => {
service.admin$.pipe(
filter(admin => !!admin), // the value of admin has to be true for it to go into the subscribe block
).subscribe(x => {
expect(x).toBeTrue();
done(); // add the done function as an argument and call it to ensure
}); // test execution made it into this subscribe and thus the assertion was made
// Calling done, tells Jasmine we are done with our test.
service.queryAdmin()
const req = httpMock.expectOne({url: `${ProducerService.baseUrl}/admin`, method: 'GET'})
req.flush(true)
}))

Had to do multiple things here. Couldn't use async or it didn't like the done method. Had to do the filter like #AliF50 suggested, and I had to pass in a value of 1 instead of true. So I ended up with this for the test:
it('should emit true when an admin', (done) => {
service.admin$
.pipe(filter(x => x))
.subscribe(x => {
expect(x).toBeTrue()
done()
})
service.queryAdmin()
const req = httpMock.expectOne({url: `${ProducerService.baseUrl}/admin`, method: 'GET'})
req.flush(1)
})
That also meant I had to modify my queryAdmin method so that I did the !! like so:
queryAdmin(): void {
// This is being done for the producer.service.spec.ts file because it
// won't decode a 'true' value automatically, so I have to pass in a 1
// as the body (i.e. a true value) and then this !!x converts that to true.
// noinspection PointlessBooleanExpressionJS
this.http.get<boolean>(`${ProducerService.baseUrl}/admin`).subscribe(x => this.admin.next(!!x))
}

Related

Angular how to combine local function return value with runtime call back http request

I have local function to check some validation which returns true/false. I also have runtime callback function which is an async function ie. http call.
Note: This checkPermission function is happening inside a for loop.
I want to check if any othese two function call is true. Can anyone help me how to achieve this?
private checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): boolean {
if(callback) {
console.log('callback function defined');
}
//following is the local function. how to make callback() here?
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
My complete code is:
Component:
options: NavOption[] = [];
this.options = this.sideNavService.loadMenus();
Sidenav service:
loadMenus(): NavOption[] {
return this.getMenus();
}
private getMenus(): NavOption[] {
const filteredMenuItems: NavOption[] = [];
let menus = [{
id: 'recorded-events',
label: 'Recorded events',
icon: 'far fa-calendar-check fa-2x',
url: `/incident/${this.organisationId}/list`,
permissions: [
EventReportingPermissions.View,
EventReportingPermissions.ViewOwnEvents,
EventReportingPermissions.ViewEmployeesEvents
],
additionalPermissionCheck: () =>
this.eventAccessGroupService.hasEventAccessGroupException()//this is the service to make http call
},
{
id: 'new-events',
label: 'Report new event',
icon: 'far fa-calendar-plus fa-2x',
url: `/incident/${this.organisationId}/create`,
permissions: [EventReportingPermissions.Report]
}]
for(let item of menus) {
let canAccess = this.checkPermissions(
topLevelItem.module,
subItem.permissions
);
filteredMenuItems.push(item);
}
return filteredMenuItems;
}
//local function
private checkPermissions(moduleId: number, permissions: number[]): boolean {
//following returns value from local function and no http call
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
//additionalPermissionCheck?: () => Observable<boolean>;
I am not sure I am understanding correctly but is your callback the function that performs the permission checking?
If so you can use a map pipe:
// Beware this returns Observable<boolean> and not boolean
const safeCallbackResult = callback ? callback() : of(true) // default to returning true as we'd like to check for the second condition
return callback().pipe(
map(canDoAction => canDoAction ? this.userSecurityService.userHasLicenseAndPermission(...) : false)
)
If you'd like to return a boolean, you can't. Because the moment you need to await for the callback's observable emission that is an operation that can take some time. Even though you could make the function async
private async checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): Promise<boolean> {
// callback().toPromise() if using RxJS 6
// firstValueFrom(callback()) if using RxJS 7
if(callback && ! (await callback().toPromise())) return false
return this.userSecurityService.userHasLicenseAndPermission(...)
}
Something like this:
sub = myHttpGetCall$().subscribe(value => {
if (value && localValue) {
// do whatever when both are true
}
}
Where localValue is the return value from your local function, which I assume is not an async operation.
Use an RxJs iif https://www.learnrxjs.io/learn-rxjs/operators/conditional/iif
booleanObservable$ = iif(() => yourLocalCondition, yourHttpRequest$, of(false));
If your localCondition is true it will make the http request otherwise there is no point so it just retuns an observable that emits false.

RxJs test for multiple values from the stream

Given the following class:
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
export class ObjectStateContainer<T> {
private currentStateSubject = new BehaviorSubject<T>(undefined);
private $currentState = this.currentStateSubject.asObservable();
public $isDirty = this.$currentState.pipe(map(t => t !== this.t));
constructor(public t: T) {
this.update(t);
}
undoChanges() {
this.currentStateSubject.next(this.t);
}
update(t: T) {
this.currentStateSubject.next(t);
}
}
I would like to write some tests validating that $isDirty contains the value I would expect after performing various function invocations. Specifically I would like to test creating a variable, updating it and then undoing changes and validate the value of $isDirty for each stage. Currently, I've seen two way of testing observables and I can't figure out how to do this test with either of them. I would like the test to do the following:
Create a new ObjectStateContainer.
Assert that $isDirty is false.
Invoke update on the ObjectStateContainer.
Assert that $isDirty is true.
Invoke undoChanges on the ObjectStateContainer.
Assert that $isDirty is false.
import { ObjectStateContainer } from './object-state-container';
import { TestScheduler } from 'rxjs/testing';
class TestObject {
}
describe('ObjectStateContainer', () => {
let scheduler: TestScheduler;
beforeEach(() =>
scheduler = new TestScheduler((actual, expected) =>
{
expect(actual).toEqual(expected);
})
);
/*
SAME TEST AS ONE BELOW
This is a non-marble test example.
*/
it('should be constructed with isDirty as false', done => {
const objectStateContainer = new ObjectStateContainer(new TestObject());
objectStateContainer.update(new TestObject());
objectStateContainer.undoChanges();
/*
- If done isn't called then the test method will finish immediately without waiting for the asserts inside the subscribe.
- Using done though, it gets called after the first value in the stream and doesn't wait for the other two values to be emitted.
- Also, since the subscribe is being done here after update and undoChanges, the two previous values will already be gone from the stream. The BehaviorSubject backing the observable will retain the last value emitted to the stream which would be false here.
I can't figure out how to test the whole chain of emitted values.
*/
objectStateContainer
.$isDirty
.subscribe(isDirty => {
expect(isDirty).toBe(false);
expect(isDirty).toBe(true);
expect(isDirty).toBe(false);
done();
});
});
/*
SAME TEST AS ONE ABOVE
This is a 'marble' test example.
*/
it('should be constructed with isDirty as false', () => {
scheduler.run(({ expectObservable }) => {
const objectStateContainer = new ObjectStateContainer(new TestObject());
objectStateContainer.update(new TestObject());
objectStateContainer.undoChanges();
/*
- This will fail with some error message about expected length was 3 but got a length of one. This seemingly is happening because the only value emitted after the 'subscribe' being performed by the framework is the one that gets replayed from the BehaviorSubject which would be the one from undoChanges. The other two have already been discarded.
- Since the subscribe is being done here after update and undoChanges, the two previous values will already be gone from the stream. The BehaviorSubject backing the observable will retain the last value emitted to the stream which would be false here.
I can't figure out how to test the whole chain of emitted values.
*/
const expectedMarble = 'abc';
const expectedIsDirty = { a: false, b: true, c: false };
expectObservable(objectStateContainer.$isDirty).toBe(expectedMarble, expectedIsDirty);
});
});
});
I'd opt for marble tests:
scheduler.run(({ expectObservable, cold }) => {
const t1 = new TestObject();
const t2 = new TestObject();
const objectStateContainer = new ObjectStateContainer(t1);
const makeDirty$ = cold('----(b|)', { b: t2 }).pipe(tap(t => objectStateContainer.update(t)));
const undoChange$ = cold('----------(c|)', { c: t1 }).pipe(tap(() => objectStateContainer.undoChanges()));
const expected = ' a---b-----c';
const stateValues = { a: false, b: true, c: false };
const events$ = merge(makeDirty$, undoChange$);
const expectedEvents = ' ----b-----(c|)';
expectObservable(events$).toBe(expectedEvents, { b: t2, c: t1 });
expectObservable(objectStateContainer.isDirty$).toBe(expected, stateValues);
});
What expectObservable does is to subscribe to the given observable and turn each value/error/complete event into a notification, each notification being paired with the time frame at which it had arrived(Source code).
These notifications(value/error/complete) are the results of an action's task. An action is scheduled into a queue. The order in which they are queued is indicated by the virtual time.
For example, cold('----(b|)') means: at frame 4 send the value b and a complete notification.
If you'd like to read more about how these actions and how they are queued, you can check out this SO answer.
In our case, we're expecting: a---b-----c, which means:
frame 0: a(false)
frame 4: b(true)
frame 10: c(false)
Where are these frame numbers coming from?
everything starts at frame 0 and that at moment the class is barely initialized
cold('----(b|)) - will emit t2 at frame 4
cold('----------(c|)') - will call objectStateContainer.undoChanges() at frame 10

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);
}));
});

Angular 6 unit test rxjs 6 operator tap unit test interceptor

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...

Resources