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);
}));
});
Related
What I have been playing with is to use combineLatest with concatAll() but they are still being called simultaneously. I could just loop and call each but I am always wondering if there is a better way within the RXJS workflow.
combineLatest(arrayOfApiObservables).pipe(concatAll()).subscribe();
The problem here is that you use the combineLatest operator, which will emit value only after all observables had emitted (e.g. it is calling everything simultaneously).
After that the concatAll can't affect the arrayOfApiObservables because they have alredy been called.
The right aproach is to create a higher-order observable (observable that emits observables), which can be achived with the help of the operator from and after that you can concatAll them to achive the desired result.
concatAll definition as seen in the docs: Converts a higher-order Observable into a first-order Observable by concatenating the inner Observables in order..
let {
interval,
from
} = rxjs
let {
take,
concatAll,
mapTo
} = rxjs.operators
let ref = document.querySelector('#container')
const obs1$ = interval(1000).pipe(take(1), mapTo('obs1'));
const obs2$ = interval(500).pipe(take(1), mapTo('obs2'));
const obs3$ = interval(2000).pipe(take(1), mapTo('obs3'));
let allObservables$ = from([obs1$, obs2$, obs3$])
allObservables$.pipe(
concatAll()
).subscribe((x) => {
console.log(x)
container.innerHTML += `<div>${x}</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
<div id="container"></div>
If you want them to be executed in a sequential manner, you could use concatMap() inside pipe().
Something like this:
of(1)
.pipe(
concatMap(result => {
console.log(result);
return of(2);
}),
concatMap(result => {
console.log(result);
return of(3);
}),
concatMap(result => {
console.log(result);
return of(4);
}),
concatMap(result => {
console.log(result);
return of(5);
}),
concatMap(result => {
console.log(result);
return of(6);
})
)
.subscribe(res => {
console.log("finish");
});
But, if you want to execute them all at once and then await for them until they are all completed, then just use forkJoin().
I'm building a web scraper that uses puppeteer. I'd obviously like to ensure that I don't break things as I work the kinks out and so I'm writing some implementation tests.
How would I go about testing out the code below? The issue is that newPage() is nested and I can't figure out how to create a spy for it.
Any ideas? Should I structure the code differently to make it easier to test (from what I've read this a big no-no). Happy to hear your suggestions.
//myFile
myFn(){
let browser = puppeteer.launch()
let page = browser.newPage();
}
describe('searchAddress', () => {
beforeEach(() => {
browserSpy = spyOn(puppeteer,'launch')
pageSpy = spyOn(puppeteer,'newPage') // <--- ????
})
it('should ensure the calls were made', async () => {
await myFn()
expect(sleepSpy).toHaveBeenCalled();
expect(pageSpy).toHaveBeenCalled();
});
});
In this case the spyOn(puppeteer,'launch') should return an object that contains a spy object for newPage call. I mean the followings:
describe('searchAddress', () => {
let newPageSpy;
let browserSpy;
beforeEach(() => {
// ARRANGE
newPageSpy = jasmine.createSpy();
let browserMock = { newPage: newPageSpy };
browserSpy = spyOn(puppeteer, 'launch').and.returnValue(browserMock);
});
it('should ensure the calls were made', async () => {
// ACT
await myFn();
// ASSERT
expect(newPageSpy).toHaveBeenCalled();
});
});
This code:
it('should not say overspecified', async function (done) {
await new Promise(resolve => resolve());
done();
});
Causes this:Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
But...I'm not returning a Promise. I'm simply awaiting a promise.
If I change my code to this, it works:
it('should not say overspecified',function(){
return new Promise(async resolve=>{
await (new Promise(resolve=>resolve()));
resolve();
})
});
Why does it only work when I unnecessarily wrap my code in a Promise?
This code:
it('should not say overspecified', async function (done) {
await new Promise(resolve => resolve());
done();
});
Causes this: Error: Resolution method is overspecified. Specify a callback or return a Promise; not both.
Because async functions always return a Promise, by design.
In Mocha, you can either return a Promise or use done, but not both.
I'd do it like this:
// Do not pass `done` as a parameter to `it` callback.
it('should not say over specified', function() {
return new Promise(resolve => resolve())
});
or if you want to use await:
// Do not pass `done` as a parameter to `it` callback.
it('should not say overspecified', async function () {
await new Promise(resolve => resolve());
});
Here's a practical async/await example:
require('chai').should()
const getFoo = async () => 'foo'
describe('#getFoo()', () => {
it('returns foo', async () => {
const foo = await getFoo()
foo.should.equal('foo')
})
})
You should use done only for callback-based or event-based code.
It's completely unnecessary to use it with Promise-based code such as regular Promises or async/await.
Testing mixed Promise and callback based code:
If I have control over the code that I'm testing (I wrote it), I "promisify" all callback based code I use from external callback-style API's, so the entire asynchronous API of the code I'm working with always uses Promises. If sensibly used, then it obviously makes testing easier as well (by eliminating completely the need for done).
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...
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.