it('POST /direct/bulk', function () {
const file = getFile('notif-direct-bulk.csv')
sinon.stub(notificationService.constructor.prototype, 'validateNotification').resolves(true)
sinon.stub(notificationService.constructor.prototype, 'createInAppNotification').resolves(true)
sinon.stub(fileService.constructor.prototype, 'storeFile').resolves(file.path)
sinon.stub(fileService.constructor.prototype, 'deleteFile').returns(true)
notificationService.validateNotification() // it's a stub
.then((valid) => {
return fileService.storeFile() // it's a stub
})
.then((storedFilePath) => {
const reader = new require('line-by-line')(storedFilePath)
console.log(notificationService.createInAppNotification()) // it's still a stub
console.log(fileService.deleteFile()) // it's still a stub
reader
.on('line', (line) => {
// inside this scope the stubs are not working
notificationService.createInAppNotification() // calling the original method, not a stub
})
.on('end', () => {
// inside this scope the stubs are not working
fileService.deleteFile() // calling the original method, not a stub
})
})
})
Why all stub is not working inside the .on scope function?
I'm wondering why it's happening? is the .on scopes are different with the sinonjs scope?
Interesting, all stubs should work as expected.
I do not know your implementation, but I can give you this example base on your test code. I assume several classes (notificationService, fileService) to simplify and satisfy test.
Highlight:
I initiate reader based on npm line-by-line example.
on reader end, I make sure that all stubs get called.
I implement mocha asynchronous code using done.
const sinon = require('sinon');
const { expect } = require('chai');
const LineByLinereader = require('line-by-line');
// Example class to show that real method not get called.
const notificationService = {
validateNotification() {
console.log('Real validateNotification get called');
// Return the opposite value based on your test (true).
return new Promise((r) => r(false));
},
createInAppNotification() {
console.log('Real createInAppNotification get called');
// Return the opposite value based on your test (true).
return new Promise((r) => r(false));
},
};
// Example class to show that real method not get called.
const fileService = {
storeFile() {
console.log('Real storeFile get called');
// Return the opposite value based on your test (string not empty).
return new Promise((r) => r(''));
},
deleteFile() {
console.log('Real deleteFile get called');
// Return the opposite value based on your test (true).
return false;
},
};
function getFile() {
// Return real dummy file path.
return {
path: './test.txt',
};
}
// Mocha asynchronous code using done.
it('POST /direct/bulk', (done) => {
const file = getFile('notif-direct-bulk.csv');
const stubValidateNotification = sinon.stub(notificationService, 'validateNotification');
stubValidateNotification.resolves(true);
const stubCreateInAppNotification = sinon.stub(notificationService, 'createInAppNotification');
stubCreateInAppNotification.resolves(true);
const stubStoreFile = sinon.stub(fileService, 'storeFile');
stubStoreFile.resolves(file.path);
const stubDeleteFile = sinon.stub(fileService, 'deleteFile');
stubDeleteFile.returns(true);
notificationService.validateNotification() // it's a stub
.then(() => fileService.storeFile()) // it's a stub
.then((storedFilePath) => {
// Expect the result is correct with file above.
expect(storedFilePath).to.equal(file.path);
// Initiate synchronous processing of lines.
const reader = new LineByLinereader(storedFilePath);
console.log(notificationService.createInAppNotification()); // it's still a stub
console.log(fileService.deleteFile()); // it's still a stub
reader
.on('line', async () => {
// inside this scope the stubs are not working
const result = await notificationService.createInAppNotification(); // calling the original method, not a stub
expect(result).to.equal(true);
})
.on('end', () => {
// inside this scope the stubs are not working
const result = fileService.deleteFile(); // calling the original method, not a stub
expect(result).to.equal(true);
// Expect all stub get called once.
expect(stubValidateNotification.calledOnce).to.equal(true);
expect(stubCreateInAppNotification.calledOnce).to.equal(true);
expect(stubStoreFile.calledOnce).to.equal(true);
// You expect this stub to get called twice.
expect(stubDeleteFile.calledTwice).to.equal(true);
// Finish the test.
done();
});
});
});
When I run it, no real method get called.
$ npx mocha stackoverflow.js
Promise { true }
true
✓ POST /direct/bulk
1 passing (35ms)
$
Hope this helps.
Related
I am doing an Image Upload feature with Cloudinary. I'm providing an array which may contains base64coded or uploaded image which is a url :
[
"https://res.cloudinary.com/\[userName\]/image/upload/v167xxxx4/luxxxfsgasxxxxxx7t9.jpg", "https://res.cloudinary.com/doeejabc9/image/upload/v1675361225/rf6adyht6jfx10vuzjva.jpg",
"data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA.......", "data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA......."
]
I'm using this function to upload the "un-uploaded", which returns the all uploaded version:
export async function uploadImage(el: string[]) {
const partition = el.reduce(
(result: string[][], element: string) => {
element.includes("data:image/")
? result[0].push(element)
: result[1].push(element);
return result;
},
[[], []]
);
for (let i = 0; i < partition[0].length; i++) {
const data = new FormData();
data.append("file", partition[0][i]);
data.append("upload_preset", "my_preset_name");
const res = await fetch(
"https://api.cloudinary.com/v1_1/userName/image/upload",
{
method: "POST",
body: data,
}
);
const file = await res.json();
partition[1].push(file.secure_url);
console.log(partition[1]);
}
return partition[1];
}
Then I will use the return value to update the state and call the api to update database:
const uploaded = await uploadImage(el[1])
console.log(uploaded);
setFinalVersionDoc({
...chosenDocument,
[chosenDocument[el[0]]]: uploaded,
});
However, it always updates the useState before the console.log(uploaded). I thought async/await would make sure the value is updated before moving on.
The GitHub repo is attached for better picture. The fragment is under EditModal in the 'component/document' folder:
https://github.com/anthonychan1211/cms
Thanks a lot!
I am hoping to make the upload happen before updating the state.
The function is correct, but you are trying to await the promise inside the callback function of a forEach, but await inside forEach doesn't work.
This doesn't work:
async function handleEdit() {
const entries = Object.entries(chosenDocument);
entries.forEach(async (el) => { // <------ the problem
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
});
[...]
}
If you want to have the same behaviour (forEach runs sequentially), you can use a for const of loop instead.
This works (sequentially)
(execution order guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
for (const el of entries) {
// await the promises 1,2,...,n in sequence
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}
}
This also works (in parallel)
(execution order not guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
await Promise.all(entries.map(async (el) => {
// map returns an array of promises, and await Promise.all() then executes them all at the same time
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}));
[...]
}
If the order in which your files are uploaded doesn't matter, picking the parallel method will be faster/better.
I'm using vuelidate with the composition API and I don't understand why the v$.validate() works correctly when I put within methods, after setup, but not within setup.
So this works:
setup() {
// inspired from
// https://vuelidate-next.netlify.app/#alternative-syntax-composition-api
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
return {
state,
v$
}
},
methods: {
async submitForm () {
const result = await this.v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
}
but, this doesn't work:
setup() {
const state = reactive ({
// the values of the form that need to pass validation, like:
name: ''
})
const rules = computed (() => {
return {
// the validation rules
}
const v$ = useVuelidate(rules, state)
const submitForm = async () {
// **ERROR : Uncaught (in promise) TypeError: v$.$validate is not a function**
const result = await v$.$validate()
// either result: validation succeeded : axios post call
// or failure and error messages show up.
}
return {
state,
v$,
submitForm
}
}
That's a bit of a pain, because I use a composable for the axios call where the state is an argument. Would be easier to keep the entire code in one place.
Composition API
useVuelidate returns a computed, so you need to use .value when accessing any of it's properties, like $error, $validate inside the setup function.
In the template it is unwrapped for you.
So I am working on a upload function for multiple images in an array. After a lot of struggling I have finally got my upload function to work and the images are showing up in the Firebase Database. However I have yet to find out a working way to make sure my upload function completes before continuing.
Below is the part were I am calling the upload function and try to store the response in uploadurl, the uploadurl variable is later used in the dispatch function to store the url with other data.
try {
uploadurl = await uploadImages()
address = await getAddress(selectedLocation)
console.log(uploadurl)
if (!uploadurl.lenght) {
Alert.alert('Upload error', 'Something went wrong uploading the photo, plase try again', [
{ text: 'Okay' }
]);
setIsLoading(true);
return;
}
dispatch(
So the image upload function is below. This works to the point that the images are uploaded, however the .then call to get the DownloadURL is not started correctly and the .then images also is not working.
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
let imagesArray = [];
try {
Promise.all(photos)
.then(photoarray => {
console.log('all responses are resolved succesfully')
for (let photo of photoarray) {
let file = photo.data;
const path = "Img_" + uuid.v4();
const ref = firebase
.storage()
.ref(`/${uid}/${path}`);
var metadata = {
contentType: 'image/jpeg',
};
ref.putString(file, 'base64', metadata).then(() => {
ref
.getDownloadURL()
.then(images => {
imagesArray.push({
uri: images
});
console.log("Out-imgArray", imagesArray);
})
})
};
return imagesArray
})
} catch (e) {
console.error(e);
}
};
So I want to return the imagesArray, AFTER, all the photos are uploaded. So the imagesArray is then set as uploadURL in the first function? After all images URL are set in imagesArray and passed to uploadURL, only then my dispatch function to upload the rest of the data should continue. How can I make sure this is happening as expected?
I have changed this so many times now because I keep getting send to different ways of doing this that I am completely at a loss how to continue now :(
Most of your uploadImages() code was correct, however in many places you didn't return the promise from each asynchronous action.
Quick sidestep: Handling many promises
When working with lots of asynchronous tasks based on an array, it is advised to map() the array to an array of Promises rather than use a for loop. This allows you to build an array of promises that can be fed to Promise.all() without the need to initialise and push to another array.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry();
});
Promise.all(arrayOfPromises)
.then((resultsOfPromises) => {
console.log('All promises resolved successfully');
})
.catch((err) => {
// an error in one of the promises occurred
console.error(err);
})
The above snippet will fail if any of the contained promises fail. To silently ignore individual errors or defer them to handle later, you just add a catch() inside the mapped array step.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry()
.catch(err => ({hasError: true, error: err})); // silently ignore errors for processing later
});
Updated uploadImages() code
Updating your code with these changes, gives the following result:
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
// CHANGED: removed 'let imagesArray = [];', no longer needed
return Promise.all(photos) // CHANGED: return the promise chain
.then(photoarray => {
console.log('all responses are resolved successfully');
// take each photo, upload it and then return it's download URL
return Promise.all(photoarray.map((photo) => { // CHANGED: used Promise.all(someArray.map(...)) idiom
let file = photo.data;
const path = "Img_" + uuid.v4();
const storageRef = firebase // CHANGED: renamed 'ref' to 'storageRef'
.storage()
.ref(`/${uid}/${path}`);
let metadata = {
contentType: 'image/jpeg',
};
// upload current photo and get it's download URL
return storageRef.putString(file, 'base64', metadata) // CHANGED: return the promise chain
.then(() => {
console.log(`${path} was uploaded successfully.`);
return storageRef.getDownloadURL() // CHANGED: return the promise chain
.then(fileUrl => ({uri: fileUrl}));
});
}));
})
.then((imagesArray) => { // These lines can
console.log("Out-imgArray: ", imagesArray) // safely be removed.
return imagesArray; // They are just
}) // for logging.
.catch((err) => {
console.error(err);
});
};
I have a class called PostController, and I trying to test the following function create:
class PostController {
constructor(Post) {
this.Post = Post;
}
async create(req, res) {
try {
this.validFieldRequireds(req);
const post = new this.Post(req.body);
post.user = req.user;
...some validations here
await post.save();
return res.status(201).send(message.success.default);
} catch (err) {
console.error(err.message);
const msg = err.name === 'AppError' ? err.message :
message.error.default;
return res.status(422).send(msg);
}
}
My test class is:
import sinon from 'sinon';
import PostController from '../../../src/controllers/posts';
import Post from '../../../src/models/post';
describe('Controller: Post', async () => {
it.only('should call send with sucess message', () => {
const request = {
user: '56cb91bdc3464f14678934ca',
body: {
type: 'Venda',
tradeFiatMinValue: '1',
... some more attributes here
},
};
const response = {
send: sinon.spy(),
status: sinon.stub(),
};
response.status.withArgs(201).returns(response);
sinon.stub(Post.prototype, 'save');
const postController = new PostController(Post);
return postController.create(request, response).then(() => {
sinon.assert.calledWith(response.send);
});
});
});
But I'm getting the following error:
Error: Timeout of 5000ms exceeded. For async tests and hooks, ensure
"done()"
is called; if returning a Promise, ensure it resolves.
(D:\projeto\mestrado\localbonnum-back-end\test\unit\controllers\post_spec.js)
Why?
Most probably it's because misuse of sinon.stub.
You've
sinon.stub(Post.prototype, 'save');
without telling what this stub will do, so in principle this stub will do nothing (meaning it returns undefined).
IDK, why you don't see other like attempt to await on stub.
Nevertheless, you should properly configuture 'save' stub - for example like this:
const saveStub = sinon.stub(Post.prototype, 'save');
saveStub.resolves({foo: "bar"});
I would like to call an asynchronous function outside the lambda handler with by the following code:
var client;
(async () => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
});
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
The 'client' does not get initialized. The same code works perfectly if executed within the handler.
initSecrets contains a lambda invocation of getSecrets() which calls the AWS SecretsManager
Has anyone an idea how asynchronous functions can be properly called for initialization purpose outside the handler?
Thank you very much for your support.
I ran into a similar issue trying to get next-js to work with aws-serverless-express.
I fixed it by doing the below (using typescript so just ignore the :any type bits)
const appModule = require('./App');
let server: any = undefined;
appModule.then((expressApp: any) => {
server = createServer(expressApp, null, binaryMimeTypes);
});
function waitForServer(event: any, context: any){
setImmediate(() => {
if(!server){
waitForServer(event, context);
}else{
proxy(server, event, context);
}
});
}
exports.handler = (event: any, context: any) => {
if(server){
proxy(server, event, context);
}else{
waitForServer(event, context);
}
}
So for your code maybe something like
var client = undefined;
initSecrets("MyWebApi").then(result => {
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET)
})
function waitForClient(){
setImmediate(() => {
if(!client ){
waitForClient();
}else{
client('Request')
}
});
}
exports.handler = async function (event, context) {
if(client){
client('Request')
}else{
waitForClient(event, context);
}
};
client is being called before it has initialised; the client var is being "exported" (and called) before the async function would have completed. When you are calling await client() the client would still be undefined.
edit, try something like this
var client = async which => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
let api = new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
return api(which) // assuming api class is returning a promise
}
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
This can be also be solved with async/await give Node v8+
You can load your configuration in a module like so...
const fetch = require('node-fetch');
module.exports = async () => {
const config = await fetch('https://cdn.jsdelivr.net/gh/GEOLYTIX/public/z2.json');
return await config.json();
}
Then declare a _config outside the handler by require / executing the config module. Your handler must be an async function. _config will be a promise at first which you must await to resolve into the configuration object.
const _config = require('./config')();
module.exports = async (req, res) => {
const config = await _config;
res.send(config);
}
Ideally you want your initialization code to run during the initialization phase and not the invocation phase of the lambda to minimize cold start times. Synchronous code at module level runs at initialization time and AWS recently added top level await support in node14 and newer lambdas: https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/ . Using this you can make the init phase wait for your async initialization code by using top level await like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
console.log("start init");
await sleep(1000);
console.log("end init");
export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
This works great if you are using ES modules. If for some reason you are stuck using commonjs (e.g. because your tooling like jest or ts-node doesn't yet fully support ES modules) then you can make your commonjs module look like an es module by making it export a Promise that waits on your initialization rather than exporting an object. Like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const main = async () => {
console.log("start init");
await sleep(1000);
console.log("end init");
const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
return { handler };
};
# note we aren't exporting main here, but rather the result
# of calling main() which is a promise resolving to {handler}:
module.exports = main();