Jest assert promise resolved with object containing - promise

Say I want to test a module that returns a Promise:
function myFunc () {
return Promise.resolve({
anArray: [1,2,3,4,5,6]
})
}
Using Jest, how can I assert the length of the array contained in the object the promise resolves to?
describe('myFunc', () => {
it('returns array of length 6', () => {
expect.assertions(1)
return expect(myFunc()).resolves // ... something here
})
})
If it were synchronous, I would do something like:
let result = myFunc()
expect(result.anArray.length).toBe(6)
How does this work with Promises?

There are two ways either return the promise from the test and make the assertion in the then or make your test using async/await
describe('myFunc', () => {
it('returns array of length 6', () => {
expect.assertions(1)
return expect(myFunc())
.then(result => expect(result).toEqual([1,2,3,4,5,6]);)
})
})
describe('myFunc',() => {
it('returns array of length 6', async() => {
const result = await expect(myFunc())
expect(result).toEqual([1,2,3,4,5,6]);)
})
})
The docs on this topic

The easiest approach is to use .resolves like you were starting to do in your sample.
You just need to chain .toMatchObject to the result:
function myFunc () {
return Promise.resolve({
anArray: [1,2,3,4,5,6]
})
}
describe('myFunc', () => {
it('returns array of length 6', () => {
expect(myFunc()).resolves.toMatchObject({ anArray: [1,2,3,4,5,6] }); // Success!
})
})
This will assert that the object has at least the anArray property set to [1,2,3,4,5,6] (it can have other properties as well).
Note that PR 5364 makes it so resolves validates its arguments synchronously so you don't even have to return, await, or use done if you are using Jest >= v22.2.0.
Update
Sounds like the goal is to only assert on the length of the array.
For that you would need to get the result of the Promise (as has been described in the previous answers), then use .toHaveLength to assert the length of the anArray property:
describe('myFunc', () => {
it('returns array of length 6', async () => {
const result = await myFunc();
expect(result.anArray).toHaveLength(6); // Success!
})
})

A way to do this is to pass a done callback, to mark your test as asynchronous and force jest to wait until you call done():
describe('myFunc', () => {
it('returns array of length 6', (done) => {
expect.assertions(1)
myFunc().then((values) => {
expect(values).toEqual([1,2,3...]);
done();
});
})
})
You can just return a Promise as well, without the need for done:
describe('myFunc', () => {
it('returns array of length 6', () => {
expect.assertions(1)
return myFunc().then((values) => {
expect(values).toEqual([1,2,3...]);
});
})
})
You can read more about this here.

Related

JS Cypress: unable to use alias for array

I am quite new to Cypress and I have some before() calling commands that create bunch of things via API calls and return the IDs of created which I use in the after() for removing them, but somehow it works perfectly if I only return one ID and store in the alias but will fail if I store an array of IDs in alias, is this intended or I did something wrong.
in my code:
before(() => {
cy.setupEnv()
.as('access_token')
.then((token) => cy.setupFlow(token).as('data_id'))
})
after(function () {
console.log(this.access_token)
console.log(this.data_id)
})
console.log(this.data_id) shows fine if setupFlow returns only one ID but becomes undefined if I try to return [id1,id2,id3]and store the array using .as("data_id")
You've struck a strange issue, worth raising with Cypress.
It only seems to happen if you have more than one test.
For example, if I run the following it logs the array.
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
return [1,2,3]
}).as('data_id')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // [1,2,3]
})
it('test1', () => {
console.log('test1')
expect(true).to.eq(true)
})
If I add a test it logs undefined!
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
return [1,2,3]
}).as('data_id')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // undefined
})
it('test1', () => {
console.log('test1')
expect(true).to.eq(true)
})
it('test2', () => {
console.log('test2')
expect(true).to.eq(true)
})
One way around this is to use Cypress.env() instead
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
Cypress.env('data_id', [1,2,3])
return [1,2,3]
}).as('data_id')
console.log('before')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // undefined
console.log(Cypress.env('data_id')) // [1,2,3]
})
beforeEach(function() {
console.log(cy.state())
console.log(this.data_id)
cy.wrap(this.data_id).as('data_id')
})
it('test1', () => {
expect(true).to.eq(true)
console.log('test1')
})
it('test2', () => {
console.log('test2')
expect(true).to.eq(true)
})
Assuming that cy.setupFlow(token) generates an array of values something like [id1, id2, id3]. This will work even when there is one value in the array. You after each should look this:
after(function () {
cy.get('#data_id').then((data_id) => {
//Get individual values
cy.log(data_id[0])
cy.log(data_id[1])
cy.log(data_id[2])
//Get all values using forEach
data_id.forEach((id) => {
cy.log(id) //get all values one by one
})
})
})
I created a small POC for this and it is working as expected.Below are the results.
Code:
describe('SO Ques', () => {
before(function () {
cy.wrap([1, 2, 3]).as('array')
})
it('SO Ques', function () {
cy.log('Hello')
})
after(function () {
cy.get('#array').then((array) => {
cy.log(array[0])
cy.log(array[1])
cy.log(array[2])
})
})
})
Result:

how to access previous mergeMap values from rxjs

I am learning to use RXJS. In this scenario, I am chaining a few async requests using rxjs. At the last mergeMap, I'd like to have access to the first mergeMap's params. I have explored the option using Global or withLatest, but neither options seem to be the right fit here.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => {
return readCSVFile(gauge.id);
}),
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id))),
catchError(error => of(`Bad Promise: ${error}`))
);
readCSVFile is an async request which returns an observable to read CSV from a remote server.
readStringToArray is another async request which returns an observable to convert string to Arrays
transposeArray just does the transpose
uploadToDB is async DB request, which needs gague.id from the first mergeMap.
How do I get that? It would be great to take some advice on why the way I am doing it is bad.
For now, I am just passing the ID layer by layer, but it doesn't feel to be correct.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id)),
mergeMap(({ data, gaugeId }: any) => readStringToArray(data, gaugeId)),
map(({ data, gaugeId }) => transposeArray(data, gaugeId)),
mergeMap(({ data, gaugeId }) => uploadToDB(data, gaugeId)),
catchError(error => of(`Bad Promise: ${error}`))
);
Why don't you do simply this?
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id)))
)),
catchError(error => of(`Bad Promise: ${error}`))
);
You can also wrap the inner observable in a function:
uploadCSVFilesFromGaugeID(gaugeID): Observable<void> {
return readCSVFile(gaugeID).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gaugeID))
);
}
In order to do this at the end:
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => uploadCSVFileFromGaugeID(gauge.id)),
catchError(error => of(`Bad Promise: ${error}`))
);
MergeMap requires all observable inputs; else, previous values may be returned.
It is a difficult job to concatenate and display the merging response. But here is a straightforward example I made so you can have a better idea. How do we easily perform sophisticated merging.
async playWithBbservable() {
const observable1 = new Observable((subscriber) => {
subscriber.next(this.test1());
});
const observable2 = new Observable((subscriber) => {
subscriber.next(this.test2());
});
const observable3 = new Observable((subscriber) => {
setTimeout(() => {
subscriber.next(this.test3());
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
let result = observable1.pipe(
mergeMap((val: any) => {
return observable2.pipe(
mergeMap((val2: any) => {
return observable3.pipe(
map((val3: any) => {
console.log(`${val} ${val2} ${val3}`);
})
);
})
);
})
);
result.subscribe({
next(x) {
console.log('got value ' + x);
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
},
});
console.log('just after subscribe');
}
test1() {
return 'ABC';
}
test2() {
return 'PQR';
}
test3() {
return 'ZYX';
}

Mocha not registering 'it' blocks inside promise list

I'm trying to write a test that will run a GET over all items. To do this, I get that list in the before block, then I want to have an it block for each item. I am trying to do this by putting the it block inside itemList.forEach. However, I suspect that the problem here is that the blocks never get registered for the test. How can I run this test as desired?
let token;
let itemList;
describe('GET items/:itemId with Admin', async () => {
before(async () => {
// NOTE: item.find({}) returns a promise of a list of all items
itemList = await item.find({});
console.log(item[0]._id) // this logs correctly!
const res = await userLogin(admin);
token = res.body.accessToken.toString();
});
it('registers initial it test', () => {
// This test passes and logs the statement
console.log('first test registered')
console.log(itemList.length) // successfully logs non-zero value
})
await itemList.forEach(async (item) => {
it('respond with json with a item', () => {
const itemId = item._id;
return getItem(itemId, token)
.then((response) => {
assert.property(response.body, '_id');
});
});
});
});
Afaik the before setup runs before every it test. It doesn't run immediately, and definitely does not wait for anything until you try to iterate your itemList. I think you will need to do either
describe('GET items/:itemId with Admin', async () => {
let token;
before(async() => {
const res = await userLogin(admin);
token = res.body.accessToken.toString();
});
// a list of all items for which tests should be created
const itemList = await item.find({});
console.log(itemList.length) // successfully logs non-zero value
for (const item of itemList) {
it('responds with json for item '+item, () => {
const itemId = item._id;
return getItem(itemId, token).then((response) => {
assert.property(response.body, '_id');
});
}
});
or
describe('GET items/:itemId with Admin', () => {
let itemList;
let token;
before(async() => {
[itemList, token] = await Promise.all([
item.find({}),
userLogin(admin).then(res => res.body.accessToken.toString())
]);
});
it('responds with json for every item', () => {
return Promise.all(itemList.map(item => {
const itemId = item._id;
return getItem(itemId, token)
.then((response) => {
assert.property(response.body, '_id');
});
});
}));
});
});
This is the solution I ended up with. I ended up putting a new describe block in the before block. The before block results the promise that gives the list of items. There is an it block in the top level so that mocha registers the test in the first place.
describe('GET items/:itemId with Admin', async () => {
before((done) => {
Item.find({}).then(async (itemList) => {
// create the admin user to get the items with
await createUsers([admin]);
const res = await userLogin(admin);
const token = res.body.accessToken.toString();
itemList.forEach((item, index) => {
const itemId = item._id;
describe(`get item number ${index}: _id: ${itemId}`, () => {
it('responds with item id', () =>
getItem(item, token)
.expect(200)
.then((response) => {
assert.notProperty(response.body, 'error');
assert.property(response.body, '_id');
assert.equal(response.body._id, itemId);
}));
});
});
done();
});
});
// If there is no it block here, it will not run the before block!
it(`register the initial it`, () => {
assert.equal('regression test!', 'regression test!');
});
});

Forkjoin with empty (or not) array of observables

I'm trying to detect when all my observables have completed. I have the following Observables:
let observables:any[] = [];
if(valid){
observables.push(new Observable((observer:any) => {
async(()=>{
observer.next();
observer.complete();
})
}))
}
if(confirmed){
observables.push(new Observable((observer:any) => {
async(()=>{
observer.next();
observer.complete();
})
}))
}
Observable.forkJoin(observables).subscribe(
data => {
console.log('all completed');
},
error => {
console.log(error);
}
);
I need to do something whenever all my functions are completed. Forkjoin seems to work when the observables array is not empty. But when the array is empty, it never gets called. How can I solve this?
you are missing the 3rd callback in subscribe. try this:
Rx.Observable.forkJoin([]).subscribe(
val => {
console.log('next');
},
err => {
console.log('err');
},
() => {
console.log('complete')
}
);
forkJoin on empty array completes immediately.
Updated for RxJS 6:
let rep: Observable<any>[] = [];
for (let i = 0; i < areas.length; i++) { // undetermined array length
rep.push(this.httpService.GET('/areas/' + areas[i].name)); // example observable's being pushed to array
}
if (rep !== []) {
forkJoin(rep).subscribe(({
next: value => {
console.log(value)
}
}));
}
Try this:
import { forkJoin, Observable, of } from 'rxjs';
export function forkJoinSafe<T = any>(array: Observable<T>[]): Observable<T[]> {
if (!array.length) {
return of([])
}
return forkJoin<T>(array);
}
You're missing complete callback. You can pass the third argument or pass an observer object instead of 3 arguments to make event checking more readable.
yourObservable.subscribe({
next: value => console.log(value),
error: error => console.log(error),
complete: () => console.log('complete'),
});

Chaining AJAX requests - Angular 2 and ES2015 Promises

In Angular 1 I could do something like:
(pseudo code)
/* part 1 */
function myFn1(){
var x = $http.get('myurl');
x.then(
() => {}, // do something here
() => {} // show error here
);
return x;
}
/* part 2 */
myFn1().then(()=>{
$q.all($http.get('url'), $http.get('url2'), $http.get('url3'))
.then(()=>{ /* do something */ });
});
I know how to replicate part 1 in Angular 2
let myFn = () => {
return new Promise((res, rej) => {
this.http.get('myurl')
.subscribe((success) => {
// do something here
res(success);
}, (error) => {
// show error here
rej(error);
});
});
}
However the code in 2nd example looks much uglier and much less readable for me.
Question 1: Can I do it better/nicer way?
Following that logic I can wrap all GET requests (part 2) in promises and than chain it but again this doesn't seem to be nice and clean way of doing that.
Question 2: how I can nicely chain requests in angular 2 without wrapping every single request in promise.
You can leverage observables for this. It's not necessary to use promises...
In series (equivalent to promise chaining):
this.http.get('http://...').map(res => res.json())
.flatMap(data => {
// data is the result of the first request
return this.http.get('http://...').map(res => res.json());
})
.subscribe(data => {
// data is the result of the second request
});
In parallel (equivalent to Promise.all):
Observable.forkJoin([
this.http.get('http://...').map(res => res.json()),
this.http.get('http://...').map(res => res.json())
])
.subscribe(results => {
// results of both requests
var result1 = results[0];
var result2 = results[1];
});
Regarding error handling and part1, you can migrate things like this:
/* part 1 */
function myFn1(){
return this.http.get('myurl').map(res => res.json())
.map(data => {
// do something
return data;
})
.do(data => {
// do something outside the data flow
})
.catch(err => {
// to throw above the error
return Observable.throw(err);
});
}
As for part2, you can still use Promises:
myFn1().then(() => {
return Promise.all(
$http.get('url').toPromise().then(res => res.json()),
$http.get('url2').toPromise().then(res => res.json()),
$http.get('url3').toPromise().then(res => res.json())
).then(() => {
/* do something */
});
});

Resources