I'd like to test my koa API routes using supertest and check what's in DynamoDB before and after to make sure that the end point did what was intended.
// app related
const pool = require('../../src/common/pool');
const app = require('../../server');
// for testing
const uuid = require('uuid');
const supertest = require('supertest');
// listen on port 40002
const request = supertest.agent(app.listen(4002));
describe('test', () => {
it.only('should', async (done) => {
debugger;
const id = uuid.v4().replace(/-/g, '');
await pool.add(id, 'data', 30);
return request
.get('/api/1')
.expect(204)
// .then(async (res) => {
// .then((res) => {
.end((res) => {
// still returns 'data' instead of 'dataNew' after the route is hit
const record = await pool.get(id);
debugger;
done();
});
});
});
In the code above, I'm creating a record in the db, then I hit the end point, and I tried a then() and an end() chained function to check the db once again. The end point will just data to dataNew and in the then() function, it still returns the original data.
Any ideas on how I can verify the new record in the db ?
References:
Supertest: Verify database after request - In TLDR at the bottom, the solution was to use co. I tried this and had issues probably cause I'm using await instead of generators.
The above was fixed by chaining the pool.add() which returns a promise, to the supertest request and then awaiting the record to verify it. Sometimes it still gets the record too quickly because the pool.update() method is not awaited within the end point that request is hitting.
describe('test', () => {
it.only('should', async () => {
const id = uuid.v4().replace(/-/g, '');
await pool.add(id, 'data', 30).then(() => {
return request
.get('/api/1')
.expect(204)
// check custom headers
//.expect('pool-before', 'data')
//.expect('pool-after', 'dataModified')
.then(async (res) => {
const record = await pool.get(id);
debugger;
expect('dataModified').to.equal(record.fields.S);
});
});
});
});
The only other way I can think of is to check the value via a custom header, a delay, or use a mock.
Let me know if anyone has a better solution.
Related
I have a Node.js AWS Lambda function created via the serverless framework. I have multiple helper functions inside it. I am having an issue with one of them due to being async. The function runs and logs out all parts I put comments next to however it doesn't update callDuration. I think that the code is having an issue due to async where it finishes in the wrong order. My goal is to be able to return the callDuration to my main function for further processing. How can I get all code to process/run and be able to meet my goal and have the code run in the right order
Here is the function:
const callAggregate = async (billingData, billingDB) => {
const accountSid = process.env.TWILIO_ACCOUNT_SID
const authToken = process.env.TWILIO_AUTH_TOKEN
const client = require('twilio')(accountSid, authToken)
// Setup model
const Billing = billingDB.model('Billing')
await Billing.findOne({_id: billingData._id}).exec().then(bill => {
const callArray = bill.callSid
console.log(bill) // This logs out
let callDuration = 0
for (const call of callArray) {
console.log(call) // This logs out
client.calls(call)
.fetch()
.then(callDetails => {
console.log(callDetails) // This logs out
callDuration += callDetails.duration
})
}
console.log(`Billing for ${callDuration} minutes of voice calling for ${billingData._id}`) // This logs out
Billing.findOneAndUpdate(
{_id: billingData._id},
{ $inc: { call_duration: callDuration }, callSid: []},
(err, doc) => {
if(err) {
console.log(err)
}
}
)
return callDuration
})
}
This is a case of mixing and matching promises with plain callbacks and mixing await with .then(), both of which make proper flow-control and error handling management difficult.
Inside your function which is async and uses await in some places, you also have a promise you are not awaiting (which means it runs open loop and nothing waits for it) and you have a database function that is using a plain callback, not the promise interface so nothing waits for it either.
More specifically, nothing is waiting for this:
client.calls(call).fetch()
So, because of not waiting for the .fetch() to finish, you were attempting to use the variable callDuration before the code was done modifying that variable (giving you the wrong value for it).
Similarly, nothing is waiting for Billing.findOneAndUpdate(...) to complete either.
A clean solution is to switch everything over to promises and await. This involves, using only promises with your database (no plain callbacks) and converting the .then() handlers into await.
async function callAggregate(billingData, billingDB) {
const accountSid = process.env.TWILIO_ACCOUNT_SID
const authToken = process.env.TWILIO_AUTH_TOKEN
const client = require('twilio')(accountSid, authToken)
// Setup model
const Billing = billingDB.model('Billing')
let bill = await Billing.findOne({ _id: billingData._id }).exec();
const callArray = bill.callSid
console.log(bill) // This logs out
let callDuration = 0
for (const call of callArray) {
console.log(call) // This logs out
let callDetails = await client.calls(call).fetch();
console.log(callDetails) // This logs out
callDuration += callDetails.duration
}
console.log(`Billing for ${callDuration} minutes of voice calling for ${billingData._id}`) // This logs out
let doc = await Billing.findOneAndUpdate({ _id: billingData._id }, { $inc: { call_duration: callDuration }, callSid: [] }).exec();
return callDuration
}
I am trying to read data from a mongoDB. Ultimately I want the data retrieved from the DB to be in json formate. My currently function goes as:
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true })
const getdata = async (): Promise<string[]> => {
let alldata: string[]
try {
await client.connect()
const database = client.db('data')
const collection = database.collection('datacollection')
const cursor = collection.find({ type: 'featureName' })
alldata = await cursor.toArray()
//console.log(alldata) //This correctly sends the required data to the console.
} finally {
void client.close()
}
//console.log(alldata) //This correctly sends the required data to the console.
return alldata
}
const data = getdata()
console.log(data) // this sends a "Promise { pending }"
I am new to javascript/typescript, but I thought that I was doing all the correct things with an asyc function and the await's -> but that clearly is not the case. Any suggestions as to what I am doing wrong is appreciated.
Your function is returning a Promise so you need to use promise.then.
E.g.
getData().then((data) => {
console.log(data);
});
Take a look at the Promise documentation here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
So I know this question is asked a lot, but I'm trying to retrieve a variable that is created within a promise. The examples I've seen on here involve calling .then and using the data there, however what I'm trying to do involves an async function--which i cant use within the .then block.
Here's my code. I'm using the Asana API To call out a lists of tasks that are due. It successfuly logs it. But I want to save the list value from the last block as a variable that I can use elsewhere.
const asana = require('asana');
const client = asana.Client.create().useAccessToken("xxx");
client.users.me()
.then(user => {
const userId = user.id;
// The user's "default" workspace is the first one in the list, though
// any user can have multiple workspaces so you can't always assume this
// is the one you want to work with.
const workspaceId = user.workspaces[0].id;
return client.tasks.findAll({
assignee: userId,
workspace: workspaceId,
completed_since: 'now',
opt_fields: 'id,name,assignee_status,completed'
});
})
.then(response => {
// There may be more pages of data, we could stream or return a promise
// to request those here - for now, let's just return the first page
// of items.
return response.data;
})
.filter(task => {
return task.assignee_status === 'today' ||
task.assignee_status === 'new';
})
.then(list => {
console.log (util.inspect(list, {
colors: true,
depth: null
}));
})
.catch(e => {
console.log(e);
});
If you're open to rewriting your .then()'s as async/await something like this could work for you:
const fetch = require('node-fetch');
async function doit() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const json = await response.json();
console.log(json);
}
doit();
I have an api call in a react component that looks like this.
login = () => {
// <--- If I set the localStorage on this line the test passes.
apiRequest.then(res => {
localStorage.setItem('token', res.token);
});
}
To test it I have mocked the api call. I want to check that the local storage is called, so have also mocked localStorage, however, as the localStorage is set in the mocked api call it never gets called. My test code is below. Does anyone know how I can check that the local storage is set in a mocked call. I have confirmed that if I move the localStorage outside the apiRequest it works, so it is being mocked correctly, the issue is definitely that it is in the apiRequest.
// This mocks out the api call
jest.mock('./api', () => {
return {
apiRequest: jest.fn(
() =>
new Promise(resolve => {
resolve();
})
),
};
});
const localStorageMock = (() => {
const store = {};
return {
setItem: jest.fn((key, value) => {
store[key] = value.toString();
})
}
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
it('sets a token in local storage', () => {
const { getByText } = render(<Login />);
const loginButton = getByText(/login/i);
// This passes
expect(apiRequest).toBeCalledTimes(1);
// This never gets called as it is being called in the apiRequest
expect(localStorage.setItem).toBeCalledWith('token', '1234');
});
If anything is unclear let me know and I will provide more details.
localStorage.setItem is called in async way through .then
login = () => {
apiRequest.then(res => {
localStorage.setItem('token', res.token);
});
}
So mocking has nothing to help with async flow. This small part
.then(res => {
localStorage.setItem('token', res.token);
}
is just put into the end of queue(it's named microtask queue if you are interested in details)
So your test code is finished and only after that this small microtask is executed.
How could you handle that? You can write test in async way and put additional expect into dedicated microtask that will run after those with localStorage.setItem call.
You can use setTimeout(macrotask) for this:
it('sets a token in local storage', done => {
const { getByText } = renderLogin();
const loginButton = getByText(/login/i);
expect(apiRequest).toBeCalledTimes(1);
setTimeout(() => {
// runs after then(....setItem) has been called
expect(localStorage.setItem).toBeCalledWith('token');
done();
}, 0);
});
or create microtask with Promise/async/await:
it('sets a token in local storage', async () => {
const { getByText } = renderLogin();
const loginButton = getByText(/login/i);
expect(apiRequest).toBeCalledTimes(1);
await Promise.resolve(); // everything below goes into separate microtask
expect(localStorage.setItem).toBeCalledWith('token');
});
[UPD] interesting thing about await that it can be used with everything else not only Promise. And it could work like Promise.resolve(<some value here>). So in your case
it('sets a token in local storage', async () => {
const { getByText } = renderLogin();
const loginButton = getByText(/login/i);
await expect(apiRequest).toBeCalledTimes(1);
expect(localStorage.setItem).toBeCalledWith('token');
});
will work as well. But I believe it looks confusing("waaaat? does .toHaveBeenCalled() return Promise for real?!") and suspicious(it's a magic! I'm not allowed to touch that!). So it's better to choose some version with straightforward "deferring"
A common problem when you try to test async code, is that you also need async tests, try to await to the apiRequest to be resolved and then verify if the local storage was called.
Have this module.exports file which returns a function to fetch some profiles using the gender parameter as the body of the request.
Here, the function is asynchronous and waits for the fetch to return a result so it can proceed. I am using all the async js rules but still, it returns undefined.
I know that there isn't any problem in the URL or the API endpoint because I directly console logged it in .then() the promise returned by the fetch, it firstly consoles logs undefined and then it returns the original value.
Here's the code:
// Pre Configuration
const fetch = require('node-fetch')
module.exports = async (req, res, genderCode) => {
const apiURL = req.apiURL
const requestURL = `${apiURL}/featured?gender=${genderCode}`
await fetch(requestURL)
.then(res => res.json())
.then(data => {
return data._embedded.compactProfiles
})
}
Also where I call the function, I also use await there.
Can anybody tell what's wrong with it?
You haven't put a return statement in the anonymous function you export.
You await the value from the second then statement (although I'm at a loss as to why you are using then and async/await), and then do nothing with it.
module.exports = async (req, res, genderCode) => {
const apiURL = req.apiURL
const requestURL = `${apiURL}/featured?gender=${genderCode}`
const res = await fetch(requestURL);
const data = await res.json();
return data._embedded.compactProfiles
}