I am testing very basic REST api with supertest. I want to save the item id received in response body and assign it to a variable. Using this id i want to make further tests like get-item-by-id or update-item-by-id. No official documentation has covered this so a beginner friendly answer would be very helpful.
test i have written
const request = require("supertest")
let id;
describe('Products API', () => {
it('GET /products --> array of products', async () => {
return request('http://localhost:3001')
.get('/api/products')
.expect(200)
.expect('Content-Type', /json/)
.then(response => {
expect(response.body).toEqual(
expect.objectContaining({
success: true,
data: expect.any(Array)
})
)
})
})
})
Use regular JS variables
const request = require("supertest")
describe('Products API', () => {
it('GET /products --> array of products', async () => {
return request('http://localhost:3001')
.get('/api/products')
.expect(200)
.expect('Content-Type', /json/)
.then(response => {
expect(response.body).toEqual(
expect.objectContaining({
success: true,
data: expect.any(Array)
})
)
const data = response.body.data;
expect(data.length).toBeGreaterThan(0)
data.forEach(product => {
let id = product.id;
// expect data about the products
})
})
})
})
want to make further tests like get-item-by-id or update-item-by-id
You should explicitly test those endpoints, not via GET /api/products
E.g.
it('POST /products/{id} --> Updates a product', async () => {
const id = 1;
const result = request('http://localhost:3001')
.post(`/api/products/${id}`)
...
// TODO: Check the product was actually updated in the database, for example
});
More importantly, don't (or try not to) store variable state between tests.
Related
There is the following situation - when a certain page is opened, several requests are made through useEffect. These requests go in parallel to each other and are used by the interceptor. In case of an error on request, we render it with a pop-up window using react-toastify. All is well until a certain access error arrives. In this case, 3 windows are rendered with the same error, since the requests were sent at the same time. The task is to visualize this error only once, instead of 3. At the same time, it is necessary to maintain the asynchrony of requests. How could such a thing be implemented? Or maybe I don’t need to touch the requests themselves, but I need to think over the architecture for showing errors? I will be glad to any advice!
Below is a code example.
const instance = axios.create({
baseURL: BASE_URL,
headers: {
"Content-Type": "application/json",
"Access-Control-Expose-Headers": "Content-Disposition",
},
timeout: 3600000,
});
instance.interceptors.request.use(
(config) => {
const configDes = { ...config };
const token = 12345678;
if (token) {
configDes.headers.Authorization = `Bearer ${token}`;
}
return configDes;
},
(error) => Promise.reject(error)
);
instance.interceptors.response.use(
(response) => response,
(error) => Promise.reject(error)
);
const api1 = () => instance.get("api/api1");
const api2 = () => instance.get("api/api2");
const api3 = () => instance.get("api/api2");
const api1Action = () =>
api1()
.then((res) => console.log(res))
.catch((err) => notifyError(err));
const api2Action = () =>
api2()
.then((res) => console.log(res))
.catch((err) => notifyError(err));
const api3Action = () =>
api3()
.then((res) => console.log(res))
.catch((err) => notifyError(err));
const Component = () => {
useEffect(() => {
dispatch(api1Action());
dispatch(api2Action());
dispatch(api3Action());
}, []);
// ToastContainer show notifyError
return (
<ToastContainer /> )
}
I tried to think over an option related to saving the status of the error, but I realized that this would bring problems in the future, because suddenly, immediately after receiving an access error, once the user tries to go somewhere else where there will be the same error. In this case, the error will not be rendered since I saved it before and track this error so as not to show the error window.
The react-toastify library allows you to set a toast element with a toastId value. If two containers have the same ID, then only one container will appear. The library itself keeps track of duplicates of its containers.
const notifyError = (errorMessage: string | Error) =>
toast.error(errorMessage, {
position: "bottom-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
toastId: errorMessage as string,
});
Please use Promise.all when you invoke multiple async requests (assuming you would like to stop if even one request fails)
const api1 = () => instance.get("api/api1");
const api2 = () => instance.get("api/api2");
const api3 = () => instance.get("api/api2");
const Component = () => {
useEffect(() => {
dispatch(Promise.all([ api1(), api2(), api3() ]).then(results = > {
// so whatever needed - results is an array of responses
}).catch(notifyError));
}, []);
return (
<ToastContainer /> )
}
More about Promise.all and Promise.allSettled can be found here
EDIT (after more details shared in comment):
const Component = () => {
useEffect(() => {
const apiCalls = [
instance.get("api/api1"),
instance.get("api/api2"),
instance.get("api/api2"),
];
dispatch(Promise.allSettled(apiCalls).then(results = > {
const errors = results.filter(result => result.status == 'rejected').map(result => result.reason);
// decide here if one of the errors break your logic
const values = results.filter(result => result.status == 'fulfilled').map(result => result.value);
// if all ok - use values
}));
}, []);
return (
<ToastContainer /> )
}
I'm trying to make several asynchronous backend calls to generate a JSON response in my express API. Because of the nature of the API, I have 3 requests that are being made that are dependent on each other in some way.
Request 1: Returns an Array of values that are used to make request 2. Each value will be used as a mapping for the remaining requests. That is to say, it will be a unique identifier used to map the response from the requests in Request 3.
Request 2 (Parallel Batch): A request is made using each value from the Array returned in request 1. Each of these returns a value to be used in each of the Request 3s. That is to say, it's a 1-to-1
Request 3 (Parallel Batch): This request takes the response from Request 2, and makes a 1-to-1 follow up request to get more data on that specific mapping (the id from request 1)
I would like the final data I send to the consumer to look like this:
{
id1: details1,
id2: details2,
id3: details3,
...
}
Here is the code I have so far...
app.get("/artists/:artist/albums", (req, res) => {
console.log("#############")
const artistName = req.params.artist
let response = {};
let s3Promise = s3.listAlbums(artistName)
let albumDetailsPromises = []
s3Promise
.then((data) => {
data.map((album) => {
// Each album name here will actually be used as the unique identifier for
// the final response
// Build an Array of promises that will first fetch the albumId, then use
// that album id to fetch the details on the album
albumDetailsPromises.push(
discogs.getAlbumId(artistName, album).then( // Returns a promise
({ data }) => {
let masterId = data.results[0].id
let recordName = data.results[0].title
// Storing the album name to carry as a unique id alongside the promise
return [album, discogs.getAlbumDetails(masterId) // Returns a promise ]
}
)
)
})
})
.then(() => {
// When all the albumIds have been fetched, there will still exist a promise in the
// second index of each element in the albumDetailsPromises array
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
namedPromises.map(
(album) => {
let albumName = album[0] // Unique Id
let albumDetailPromise = album[1]
// Resolving the albumDetailsPromise here, and storing the value on
// a response object that we intend to send as the express response
albumDetailPromise
.then(
({ data }) => {
response[albumName] = data
})
.catch(err => response[albumName] = err)
})
})
})
.catch((err) => console.log(err))
})
As of now, everything seems to be working as expected, I just can't seem to figure out how to "await" the response object being updated at the end of all these Promises. I've omitted res.send(response) from this example because it's not working, but that's of course my desired outcome.
Any advice is appreciated! New to javascript...
I would recommend rewriting this using async/await as it helps to reduce nesting. You can also extract the logic the get the album-details into a separate function, as this also increases the readability of the code. Something like this (this still needs error-handling, but it should give you a start):
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist;
const albumNames = await s3.listAlbums(artistName);
const result = {};
const albumDetailPromises = albumNames.map(albumName => requestAlbumDetails(discogs, artistName, albumName));
const resolvedAlbumDetails = await Promise.all(albumDetailPromises);
// map desired response structure
for(const albumDetail of resolvedAlbumDetails) {
result[albumDetail.albumName] = albumDetail.albumDetails;
}
res.json(result);
});
async function requestAlbumDetails(service, artistName, albumName) {
const albumInfo = await service.getAlbumId(artistName, albumName);
const masterId = albumInfo.results[0].id;
const albumDetails = await service.getAlbumDetails(masterId);
return { albumName, albumDetails };
}
To answer your question how you could do it with your code:
You'd need to wait for all details to be fulfilled using another Promise.all call and then just send the response in the then-handler:
Promise.all(albumDetailsPromises)
.then((namedPromises) => {
const detailsPromises = namedPromises.map(
(album) => {
let albumName = album[0];
let albumDetailPromise = album[1];
return albumDetailPromise
.then(({ data }) => {
response[albumName] = data;
})
.catch(err => response[albumName] = err);
});
return Promise.all(detailsPromises)
.then(() => res.json(response));
})
Refactored using async/await...
app.get("/artists/:artist/albums", async (req, res) => {
const artistName = req.params.artist
let response = {};
let albums = await s3.listAlbums(artistName)
const promises = albums.map(async (album) => {
let result = await discogs.getAlbumId(artistName, album)
try {
let masterId = result.data.results[0].id
let tempRes = await discogs.getAlbumDetails(masterId)
return [album, tempRes.data]
} catch (error) {
return [album, { "msg": error.message }]
}
})
responses = await Promise.all(promises)
responses.map(data => { response[data[0]] = data[1] })
res.send(response)
})
The following code is working and retrieving all users from my neDB-promisses:
const getUsers = (res) => {
db.find({})
.sort({ name: 1 })
.exec()
.then(
(content) => {
res.status(200).json(content);
},
(err) => {
res.status(400).json(err);
}
);
};
What I'm trying to do: optimize this piece of code in order to avoid future repetitions on CRUD functions, something like this:
...
.then(successFunctionCall, failureFunctionCall)
...
I thought about creating a separate module called successFunctionCall/failureFunctionCall but I need to call res inside it to set the response JSON and status code. Is that a better way to achieve this ?
Thank you.
You can curry the functions. When you use them as handlers, pass res, and get a new function that waits for the content or err:
const successFunctionCall = res => content =>
res.status(200).json(content);
const failureFunctionCall = res => err =>
res.status(400).json(err);
const getUsers = (res) => {
db.find({})
.sort({ name: 1 })
.exec()
.then(
successFunctionCall(res),
failureFunctionCall(res)
);
};
In order to avoid repetitions on CRUD functions, your concerns could be separated a little differently. Below is a basic idea of what I mean.
const usersDb = {} // your users db instance here
const findAllSortedBy = db => (...args) => db.find({}).sort(...args).exec()
const findAllUsersSortedBy = findAllSortedBy(usersDb)
const success = res => content => res.status(200).json(content)
const failure = res => err => res.status(400).json(err)
const getUsers = res => {
findAllUsersSortedBy({ name: 1 })
.then(success(res))
.catch(failure(res))
}
here is getUsers in context of an express route handler
const getUsers = (req, res) => {
findAllUsersSortedBy({ name: 1 })
.then(success(res))
.catch(failure(res))
}
router.get('/users', getUsers)
I have some problems when I try to fetch the data, I didn't get response.
I write the path correctly?
I attached the part of the code and pic of my project hierarchy.
let transportation = [];
const init = () => {
fetch('/data/transportationDataCheck.json')
.then((response) => {
return response.json();
})
.then((data) => {
transportation = data;
}).then(() => {
renderList(transportation);
});
};
try this:
const data = require("../data/transportationDataCheck.json")
console.log(JSON.stringify(data));
Or you may try after changing little URL
let transportation = [];
const init = () => {
fetch('../data/transportationDataCheck.json')
.then((response) => {
return response.json();
})
.then((data) => {
transportation = data;
}).then(() => {
renderList(transportation);
});
};
You are trying to serve a static file with a fetch command, which inherently requires the file to be served by a server.
Someone had a similar issue here: Fetch request to local file not working
Depending on what type of file this is, you may not need to make a fetch. You could probably instead require the file:
var transportationDataCheck = require('./data/transportationDataCheck.json');```
Use ./ at the beginning of the path
fetch('./data/transportationDataCheck.json')
.then(response => {
return response.json()
})
.then(data => {
// Work with JSON data here
console.log(data)
})
.catch(err => {
// Do something for an error here
})
I am trying to test the call to github api using jest to see if the results are returned (the aim of this is to test my unit testing skills). But for some reasons, my code works fine but still fails my test. My suspicion is that i most likely don't understand how to write these kind of test. Below is my code
const functions = {
getUserRepo: async (username) => {
const url = `https://api.github.com/users/${username}/repos`;
console.log(url);
let result = [];
await axios.get(url)
.then(function (response) {
response.data.forEach(value => result.push(value.name));
return result;
})
.catch(function (error) {
return error;
});
}
}
This code above returns the right results in an array format but fails the test below
describe('Check repos from git api', () => {
test('Should return user repos', async () => {
await functions.getUserRepo('whitehox')
.then((response) => {
expect(response.data).toEqual([ '57','decafreelance','decases','eexport','exportchat','flisch', 'gitprac', 'itravelcentral', 'pollark', 'portfolio', 'startereit', 'talkative', 'team-portfolio'])
})
});
});
Please what is the issue with this test and how do i fix it?
Two things need to be fixed.
You need to return the result from your function. It can be simplified to this:
const functions = {
getUserRepo: (username) => {
const url = `https://api.github.com/users/${username}/repos`;
console.log(url);
return axios.get(url) // <= return the result
.then(function (response) {
return response.data.map(value => value.name);
})
.catch(function (error) {
return error;
});
}
}
...which makes response the array so test it directly:
describe('Check repos from git api', () => {
test('Should return user repos', async () => {
await functions.getUserRepo('whitehox')
.then(response => {
// response **is** the array
expect(response).toEqual(['57', 'decafreelance', 'decases', 'eexport', 'exportchat', 'flisch', 'gitprac', 'itravelcentral', 'pollark', 'portfolio', 'startereit', 'talkative', 'team-portfolio', 'YorubaIndigenous']); // Success!
})
});
});
(...and there is also a new repo called 'YorubaIndigenous', I added it to the expected value).