async function getData() {
const getProject = await axios.get('url', {
auth: {
username: 'username',
password: 'pw'
}
});
const projects = getProject.data.value;
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
})
);
const results = await axios.all(promises)
const KPIs = results.map(v => v.data.value);
}
What I am trying to do is:
get an axios call to fetch the name of the projects.
Use those fetched names to call multiple axios calls. This is supposed to give me some data from that project.
When I do the second axios call, there are some API calls that won't work because the data does not exist in that project. This was intended. But it will break by giving me the 404 error and won't reach the line const results = await axios.all(promises).
I want it to skip that whenever it doesn't exist and only call it from the one that exists and store the fetched data into KPIs. How can I do that?
EDIT
The first axios call returns an array of objects
ex)
{id: "id...", name: "Javelin", url: " .visualstudio.com/_apis/projects/6a93eab2-4996-4d02-8a14-767f02d94993", state: "wellFormed", revision: 99, …}
Something like that. So, I literally just get the .name of this object and put it into my url like:
axios.get({id}.extmgmt.visualstudio.com/_apis/ExtensionManagement/InstalledExtensions/{id}/..../${project.name}_test/Documents
This wraps the call in a promise that won't fail.
You also need to consider than you probably want to filter out null responses.
async function getData() {
const getProject = await axios.get('url', {
auth: {
username: 'username',
password: 'pw'
}
});
const projects = getProject.data.value;
const promises = projects.map(fallible);
const results = await axios.all(promises)
const KPIs = results.filter(v => v).map(v => v.data.value);
}
function fallibleCall(project) {
return new Promise((resolve, reject) => {
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).then(resolve).catch(resolve);
})
}
If the problem is with fetching project data you just need to handle errors that occur there, one way is to simply catch the error and return null instead.
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).catch(err => {
return null;
})
);
You can easily fix that by handling 404 responses by yourself and keep rejecting for all other errors as follows:
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).catch(err => {
if (err.response.status === 404) {
return null; // or an empty array or whatever you want
}
throw err;
});
);
See https://github.com/axios/axios#handling-errors for details.
Related
i have a function called login that redirects the user to the main page if everything was ok. Then, on the main page, i want to fetch some user info with useEffect using the token the was stored when the user logged in, but nothing happens. Only when i refresh the page i get the data.
login function
export const login = ({ email, password, history }) => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:5000/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (data.status === 200) {
localStorage.setItem("userToken", data.user);
history.push("/");
} else {
dispatch(
setNotification({
variant: "error",
message: data.message,
})
);
}
} catch (e) {
console.log(e.message);
}
};
};
fetch user funtion
export const fetchUser = () => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:5000/userInfo", {
headers: {
"x-access-token": localStorage.getItem("userToken"),
},
});
const data = await response.json();
dispatch(setUser({
id: data.id,
fullname: data.fullname,
email: data.email
}))
} catch (error) {}
};
};
useEffect on my main page
useEffect(() => {
dispatch(fetchUser());
}, []);
backend function
module.exports.getCurrentUser = async (req, res) => {
const token = req.headers["x-access-token"];
try {
const verifyToken = jwt.verify(token, "123");
const user = await User.findOne({ email: verifyToken.email });
return res.json({
id: user._id,
fullname: user.fullname,
email: user.email
})
} catch (error) {}
};
The 2nd parameter to useEffect tells it when it needs to run. It only runs if one of the values in the array has changed. Since you pass an empty array, none of the values in it have changed.
This is presuming your app probably starts at '/', then detects there is no user so switches to the login screen. When it goes back to the root, it only executes useEffect if something in the array has changed from the previous render.
As it is, the isMounted doesn't make much sense. This could simply be:
useEffect(() => {
dispatch(fetchUser());
});
You're calling setUser, but what is calling your login function?
I’m trying to add a test to getAllUsers function but I don’t know why the test is failing?
I think that the assertions are run before all the async call to User.findAllfinishes!!!
do you have any suggestions?
this is the file being tested:
const { Op } = require('sequelize')
const { User } = require('../models')
const catchAsync = require('../utils/catchAsync')
const AppError = require('../utils/appError')
exports.getAllUsers = catchAsync(async (req, res, next) => {
const users = await User.findAll({
attributes: ['id', 'username', 'email', 'role', 'avatar'],
where: {
id: { [Op.gt]: 0 }
}
})
if (!users.length) {
return next(new AppError('no data found', 204))
}
res.status(200).json({
status: 'success',
data: users
})
})
and this is the test code:
const userController = require('../controllers/userController')
describe('UserController', () => {
const users = [
{
username: 'Admin',
role: 'admin',
avatar: 'bb',
email: 'admin#gmail.com'
},
{
username: 'User',
role: 'user',
avatar: 'bb',
email: 'user#gmail.com'
}
]
test('Expect to respond with 200 code and users data', async () => {
const req = {}
const res = { status: jest.fn(() => res), json: jest.fn(() => res) }
const next = jest.fn()
await userController.getAllUsers(req, res, next)
expect(res.status).toHaveBeenCalledTimes(1)
expect(res.status).toHaveBeenCalledWith(200)
expect(res.json).toHaveBeenCalledTimes(1)
expect(res.json).toHaveBeenCalledWith({
status: 'success',
data: users
})
})
})
thanks for the help
Considering there's no pre-requisite (mocked database) that hasn't been shared in the question
When you run jest --coverage it will generate you a coverage report which would give you an overview of the code execution
above you can see that the if statement has not been executed, meaning that an error is thrown from User.findAll
To solve this: you can mock the User model and it's findAll's resolved value to overcome this
const userController = require('../controllers/userController');
const { User } = require('../models');
// generate auto-mock of the module
jest.mock('../models');
describe('UserController', () => {
const users = [
// .... same as in the question
];
beforeAll(() => {
// give the mock function a value
// for the promise to be resolved with
User.findAll.mockResolvedValue(users);
});
test('Expect to respond with 200 code and users data', async () => {
// .... same as in the question
});
});
I built the API with apollo server and everything works fine in graphiql. I make requests to the api from front-end react app with apollo client.
const [getUserPosts, { loading, error, data }] = useLazyQuery(GET_USER_POSTS);
useEffect(() => {
getUserProfile();
getUserPosts({ variables: { email: userEmail } });
}, [userEmail]);
SO getUserProfile fetches the user email from the express back end (I have an express serving react and a separate graphql api), then I query the posts of that user on the api. Below is the query itself
export const GET_USER_POSTS = gql`
query User($email: String) {
user(email: $email) {
email
posts {
content
}
}
}
`;
This is the typedefs and resolver on the api server
const typeDefs = gql`
type User {
email: String
posts: [Post]
}
type Post {
id: ID!
email: String
content: String
}
type Query {
users: [User]
posts: [Post]
user(email: String): [User]
post(id: String): [Post]
}
type Mutation {
addPost(email: String, content: String): [Post]
deletePost(id: String): [Post]
}
`;
const resolvers = {
Query: {
users: () => User.find(),
posts: () => Post.find(),
user: (parent, args) => User.find({ email: args.email }),
post: (parent, args) => Post.find({ _id: args.id }),
},
User: {
posts: async user => {
try {
const postsByUser = Post.find({ email: user.email });
return postsByUser;
} catch (error) {
console.log(err);
}
},
},
Mutation: {
addPost: async (parent, args) => {
const newPost = new Post({
email: args.email,
content: args.content,
});
try {
newPost.save();
} catch (err) {
console.log(err);
}
},
deletePost: async (parent, args) => {
try {
const deletedPost = await Post.deleteOne({ _id: args.id });
} catch (err) {
console.log(err);
}
},
},
};
then I try to console.log the data here
if (loading) {
console.log(loading);
}
if (error) {
console.log(error);
}
if (data) {
console.log(loading);
let test = data.user[0];
//I can see the data logged in the console as an object {email: "abc", posts: [array of posts]}
console.log(test);
}
BUT if I try to console.log(test.posts) react results with can not read property "posts" of undefined
UPDATE-1 !!
So when react results with the above error, I try to refresh the page again and it now can logs the "posts" array. But it sometimes take 2 or 3 refresh to make it work and sometimes when I refresh again it does not work anymore. Why is this happening ????
UPDATE-2 !!
So I try to troubleshoot with this line:
{data ? console.log(data.user[0].posts) : console.log("nothing")}
and interestingly it actually does log "nothing" a few times in the console before logging the data. But this is weird because I explicitly write that if only "data" is "true" then log it in the console. But somehow "data" is somtimes null itself. This data is provided by apollo client and it should be always true after loading is false, how is data still null after loading is false already ???
So I found the problem. Turns out it actually comes from within this block:
useEffect(() => {
getUserProfile();
getUserPosts({ variables: { email: userEmail } });
}, [userEmail]);
After observing in the network tab, it seems that my app try to send request to graphQL api before getUserProfile was done pulling user email, so it sent an empty request and hence received nothing. I was naive to think getUserProfile and getUserPosts will be executed synchronously. So I wrap getUserPosts with
if (userEmail) {
getUserPosts({ variables: { email: userEmail } });
}
So now only after I received the uerEmail then getUserPosts will be executed.
I'm trying to write a unit test for a redux async action creator using jest.
asyncActions.js:
const startSignInRequest = () => ({
type: START_SIGNIN_REQUEST
});
// action creator to dispatch the success of sign In
export const signInSucceded = user => ({
type: SIGNIN_USER_SUCCEEDED,
user
});
// action creator to dispatch the failure of the signIn request
export const signInFailed = error => ({
type: SIGNIN_USER_FAILED,
error
});
const signInUser = user => dispatch => {
dispatch(startSignInRequest);
return signInApi(user).then(
response => {
const { username, token } = response.data;
dispatch(signInSucceded(username));
localStorage.setItem("token", token);
history.push("/homepage");
},
error => {
let errorMessage = "Internal Server Error";
if (error.response) {
errorMessage = error.response.data;
}
dispatch(signInFailed(errorMessage));
dispatch(errorAlert(errorMessage));
}
);
};
signInApi.js:
import axios from "axios";
import { url } from "../../env/config";
const signInApi = async user => {
const fetchedUser = await axios.post(`${url}/signIn`, {
email: user.email,
password: user.password
});
return fetchedUser;
};
In the Writing tests of redux's official documentation, they use fetch-mock library. However, I think that this library call the real Api.
I tried to mock the axios api using jest mocks.
/__mocks/signInApi.js:
const users = [
{
login: 'user 1',
password: 'password'
}
];
export default function signInApi(user) {
return new Promise((resolve, reject) => {
const userFound = users.find(u => u.login === user.login);
process.nextTick(() =>
userFound
? resolve(userFound)
// eslint-disable-next-line prefer-promise-reject-errors
: reject({
error: 'Invalid user credentials',
}),
);
});
}
__tests/asyncActions.js:
jest.mock('../axiosApis/signInApi');
import * as actions from '../actions/asyncActions';
describe('Async action creators', async () => {
it('Should create SIGN_IN_USER_SUCCEEDED when signIn user has been done', () => {
const user = {
login: 'user 1',
password: 'password'
}
await expect(actions.signInUser(user)).resolves.toEqual({
user
})
})
});
The test failed and I got:
expect(received).resolves.toEqual()
Matcher error: received value must be a promise
Received has type: function
Received has value: [Function anonymous]
How can I mock this async action creator only with jest?
Looks like you need to update your mock to resolve to an object like this:
export default function signInApi(user) {
return new Promise((resolve, reject) => {
const userFound = users.find(u => u.login === user.login);
process.nextTick(() =>
userFound
? resolve({ // <= resolve to an object
data: {
username: 'the username',
token: 'the token'
}
})
// eslint-disable-next-line prefer-promise-reject-errors
: reject({
error: 'Invalid user credentials',
}),
);
});
}
...then what you are really testing is that actions.signInUser returns a function which can be called with a user...
...which then returns another function which can be called with a dispatch which dispatches the proper actions:
jest.mock('./signInApi');
import * as actions from './asyncActions';
describe('Async action creators', () => {
it('Should create SIGN_IN_USER_SUCCEEDED when signIn user has been done', async () => {
const user = {
login: 'user 1',
password: 'password'
};
const dispatch = jest.fn();
await actions.signInUser(user)(dispatch); // <= call the function on a user, then call the resulting function on a dispatch
expect(dispatch).toHaveBeenCalledTimes(2); // Success!
expect(dispatch).toHaveBeenNthCalledWith(1, { type: START_SIGNIN_REQUEST }); // Success!
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SIGNIN_USER_SUCCEEDED, user: 'the username' }); // Success!
})
});
EDIT: I have to edit my answer as the first one pointed to a wrong direction.
So from my understanding you want to mock the Action + Return value. In your case I would just immediately return the result of your mock function. As you're not mocking axios.post you don't need to wrap everything inside a promise and return that. You're not mocking only the HTTP call but the whole action.
const users = [
{
login: 'user 1',
password: 'password'
}
];
export default function signInApi(user) {
const userFound = users.find(u => u.login === user.login);
return (userFound ? userFound : {
error: 'Invalid user'
});
}
I asked a question about JS Promises in this post:
I'm doing Promises wrong... What am I missing here?
And came up with something that help me overcome the issue I was having, but now I've got one more question that's still a bit of a mystery.
In the updated code I have:
login.ts:
import { Router } from 'express-tsc';
import { db, dbUserLevel } from '../../util/db';
import * as bodyParser from 'body-parser';
import { genToken } from '../../util/token';
import * as jwt from 'jsonwebtoken';
export var router = Router();
let urlencodedParser = bodyParser.urlencoded({ extended: false });
let jsonParser = bodyParser.json();
router.post('/', jsonParser, (req, res) => {
req.accepts(['json', 'text/plain']);
let data = req.body;
console.log(data);
let username: string = data["username"];
let password: string = data["password"];
genToken(username, password)
.then(token => {
res.status(200).send(token);
})
.catch(err => {
res.status(500).send(err);
});
});
The issue I'm now having is described in the commented code of the snippet below:
token.ts :
import * as jwt from 'jsonwebtoken';
import { db, dbUserLevel } from '../util/db';
export function genToken(username, password) {
let token_payload = { user: username, admin: false };
let token_payload_admin = { user: username, admin: true };
// TODO: Add secret as an environment variable and retrieve it from there
let token_secret = 'move this secret somewhere else';
let token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
let token: Object;
let query = db.open()
.then(() => dbUserLevel('user'))
// If above is successful, this .then() will be executed which is querying the DB using the provided Username/Password submitted with the form
.then(() => db.collection('users').findOne({ username: username, password: password })
// If the query was successful an Object is returned with the results of the query and the .then() below is executed to analyze the result
.then((result) => {
if (result.isAdmin === 1) {
// If the "isAdmin" property of the returned Object is "1", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload_admin, token_secret, token_header) }
} else if (result.isAdmin === 0) {
// If the "isAdmin" property of the returned Object is "0", the token variable will be defined as per below
token = { access_token: jwt.sign(token_payload, token_secret, token_header) }
}
})
// The question is here... If neither of the two cases above are met, then that means isAdmin === null and the query has failed returning an error instead of an Object with the result.
// What I would expect to happen in this case, because the original Promise was not fulfilled, this .catch() should be called.
// Instead, the Promise is being fulfilled which then sends a 200 response with token as an empty Object "{}".
// How do I get this .catch() to reject the Promise and send the 500 response instead?
.catch(err => {
db.close();
Promise.reject(err);
}))
.then(() => {
db.close();
Promise.resolve(token);
return token;
})
.catch(err => {
db.close();
Promise.reject(err);
return err;
});
return query;
};
Your problem is that you missed to return the Promise.reject(…)s from your callbacks. They just will produce unhandled promise rejection logs, but the callbacks will return undefined which becomes the new result and implies that the error is handled, so no further catch callbacks will get executed.
However, that code should be simplified a lot anyway. Regarding the closing of the database connection, you should have a look at the disposer pattern or a finally method.
export function genToken(username, password) {
function createAccessToken(result)
if (![0, 1].includes(result.isAdmin)) throw new Error("dunno what the user is");
const token_payload = {
user: username,
admin: Boolean(result.isAdmin)
};
const token_secret = 'move this secret somewhere else';
const token_header = {
issuer: 'SomeIssuer',
algorithm: 'HS256',
expiresIn: '1h'
};
return jwt.sign(token_payload, token_secret, token_header);
}
return db.open()
.then(() => dbUserLevel('user'))
.then(() => db.collection('users').findOne({ username: username, password: password }))
.then(result => ({access_token: createAccessToken(result)}))
.then(token => {
db.close();
return token;
}, err => {
db.close();
throw err;
});
}