I am trying to find a clean way to write an integration test (unit test with jest which uses real resources) with jest using promise chaining but I can get my head around it.
In addition of actual nesting the promises correctly I also don't know how to solve those two issues:
expects
in different promises being picked up correctly by jestThere are 4 function I would like to chain, similar to the Act - Arrange - Assert pattern. The problem is that those funktion have async calls to f.e. a database and can have expectation.
To have a better understanding here is some pseudocode how this could look in my head. This only should provide a better understanding of the issue.
const setupResources = () => new Promise(resolve => {
//async calls to a database to setup a test
await dynamoDBClient.put(params).promise();
// returns data which are necessary for the other steps
resolve(data);
});
const act = (data) => new Promise(resolve => {
// doing some stuff
const otherdata = {data};
// calling some endpoint asyc
const result = await axios.post(
`https://restapi.com/test`,
JSON.stringify(otherdata),
);
// some expects
expect(result.status).toBe(200);
// again some data which will be needed in the other steps
resolve({someInformation: 'info', data}) ;
});
const assertIt = (data) => {
const prarams = {/*db related data*/}
new Promise(resolve=>{
const dbdata = await dynamoDBClient.get(params).promise();
// some expectation
expect(dbdata).toBe(data.data);
resolve();
})
};
const tearDown = (data) => {
// cleanup of data base trash which should always happen
await dynamoDBClient.delete(data.params).promise();
};
The actual test should look something along those line if that makes sense.
it('test',()=>{
return setupResources()
.then(act(data))
.then(assertIt(data))
.finally(teardown(data));
})
async..await
is a substitute for raw promises, there's no need to use then
. The use of new Promise
with existing promises is an antipattern. One of problems that async
function solves is that data that needs to be available down the promise chain doesn't need to be passed through the entire chain, it can be declared as a variable in a scope that is available for the chain.
It should be:
const setupResources = async () => {
await dynamoDBClient.put(params).promise();
...
return data;
});
And the test:
it('test', async ()=> {
let data;
try {
data = await setupResources();
...
} finally {
await teardown(data);
}
});
Setup and teardown is a task that beforeEach
and afterEach
blocks solve. If there's a need to extract them to reusable functions, common data can be shared via context object like shown here.