Mocha test fails before async function in beforeEach() completes - javascript

what kind of async madness have I got myself into?
Test fails, then it logs the access token!
import assert from "assert";
import "dotenv/config";
import { expect } from "chai";
var unirest = require("unirest");
describe("some tests", function () {
let accessToken: string | undefined;
beforeEach(function () {
const email = process.env.EMAIL;
const password = process.env.PASSWORD;
const login = async function () {
const req = await unirest(
"POST",
`${process.env.API_BASE_URL}/auth/local/login`
)
.headers({
"Content-Type": "application/json",
})
.send(JSON.stringify({ email, password }))
.end(function (res: any) {
if (res.error) throw new Error(res.error);
accessToken = JSON.parse(res.raw_body).access_token;
console.log(accessToken); // this logs after the test fails? why?
});
};
login();
});
it("should be able to login and get an access token", async function () {
expect(accessToken).to.not.be.undefined;
});
});
The error is
AssertionError: expected undefined not to be undefined
I've also tried returning login() but its still logs after it fails
beforeEach(function () {
const email = process.env.EMAIL;
const password = process.env.PASSWORD;
const login = async function () {
const req = await unirest(
"POST",
`${process.env.API_BASE_URL}/auth/local/login`
)
.headers({
"Content-Type": "application/json",
})
.send(JSON.stringify({ email, password }))
.end(function (res: any) {
if (res.error) throw new Error(res.error);
accessToken = JSON.parse(res.raw_body).access_token;
console.log(accessToken); // this logs after the test fails? why?
});
};
return login();
});

Related

How should I have all the inner methods called as I try and test my login route?

Hello I am using mocha, chai, sinon and supertest.
I have a logging route I cannot stub/spy/mock (tried all 3) an inner class method authService.findByEmail(req.body.email) inside the loginRoute function
this is the function I want to test
login.js
const jwtGenerator = (payload) => jwt.sign(payload, process.env.TOKEN, { expiresIn: "1h" });
const loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) {
throw createError(401, 'Incorrect password.');
}
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully 😊 👌", user, token });
} catch (error) {
next(error);
}
}
router.post("/auth/login", loginRoute);
Here is my test file
login.spec.js
const chai = require("chai");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
const supertest = require("supertest");
const sinon = require("sinon");
const AuthService = require("../../services/authService"); // the class
describe("/login", () => {
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
const authService = new AuthService();
let userQuery, token, response, cookie;
const agent = supertest(app);
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let adminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const user = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
it("login controller function", async () => {
token = createAccessToken(user);
// I have commented out the set as they aren't doing anything atm
response = await agent.post("/auth/login").send(loginDetail)//.set("Accept", "application/json")
// .set("Cookie", `access-token=${token}`);
userQuery = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(adminUser);
// authService.findByEmail(loginDetail.email); // even if I call it the calledWith might work but response.status is still 500
// userQuery.resolves(adminUser);
expect(userQuery).to.have.been.calledWith(loginDetail.email); // is never called
// expect(userQuery).to.have.been.called // never called
expect(response.status).to.deep.equal(200); // returns 500
});
});
I must be doing something wrong somewhere I have tried everything I dont know how to have all the inner methods/functions inside the loginRoute to be called automatically as I call the loginRoute through supertest(app).post("/auth/login")

How to throw a server error when fetching JS

I'm new in JavaScript and i have a task to post an email input from form to a node server,everything works fine,but i should implement this functionality:
When an email is forbidden#gmail.com, the server responds with the 422 status code and payload which contains the information about the error. Use browser developer tools to examine the response for this scenario. Display the error message in the browser using window.alert().
I created a customException,it gives me the error in the console,but the server still responds with the 200 status code,but as i understand,it should give an error and the post should not work.How to do this task,i have no idea..?
Fetch functions:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput);
// if (emailInput === 'forbidden#gmail.com'){
// throw new CustomException('422');
// }
}
}
const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {
'Content-Type': 'application/json'
} : {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!');
error.data = errResData;
throw error;
});
}
return response.json();
});
};
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
console.log(responseData);
}).catch(err => {
console.log(err, err.data);
});
}
function CustomException(message) {
const error = new Error(message);
error.code = "422";
window.alert('Forbidden email,please change it!')
return error;
}
CustomException.prototype = Object.create(Error.prototype);
Validate function:
const VALID_EMAIL_ENDINGS = ['gmail.com', 'outlook.com', 'yandex.ru']
export const validateEmail = (email) => !!VALID_EMAIL_ENDINGS.some(v => email.includes(v))
export { VALID_EMAIL_ENDINGS as validEnding }
Please help.Thanks in advance!
Something like this should work:
Server code:
Simplify validate function.
export const isValid = (email) => {
if (email === 'forbidden#gmail.com') {
return false
}
return true
}
Then on your route, something like this, assuming expressjs behind.
app.post('/subscribe', (req, res, next) => {
const email = req.body.email
if (!isValid(email)) {
return res.status(433).send('Email is forbidden')
}
return res.status(200).send('Success')
})
In your frontend you can just post to /subscribe with the email payload
const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {
'Content-Type': 'application/json'
} : {}
})
.then(response => response.json())
};
And in your sendData you can catch the error, like you're doing
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
console.log(responseData);
}).catch(err => {
console.log(err); // Email is forbidden
window.alert('Boo!')
});
}
Sidenote: In most cases prototyping should be avoided in javascript.

testing login api with jest

how to test this Api and get 100% score of testing coverage?
const login = async (email, password) => {
axios
.post('https://conduit.productionready.io/api/users/login', {
user: {
email,
password,
},
})
.then((response) => response);
};
Your function is relatively simple : one path, no branching logic, one external call.
All your function do is calling an endpoint through axios.post.
login.js
export const login = async (email, password) => {
/*
* Notice that I added the 'await', else 'async' is useless.
* Else you can directly return the axios.post method.
*/
await axios
.post('https://conduit.productionready.io/api/users/login', {
user: {
email,
password,
},
})
.then((response) => response); // This line is also useless for the moment
};
login.spec.js
import { login } from './login';
// Mock axios, else you will really request the endpoint
jest.mock('axios');
import axios from 'axios';
describe('Login tests', () => {
describe('login function', () => {
const email = 'test#test.com';
const password = 'password';
beforeEach(() => {
/*
* Not necessary for the moment, but will be useful
* to test successful & error response
*/
axios.post.mockResolvedValue({});
});
it('should call endpoint with given email & password', async () => {
await login(email, password);
expect(axios.post).toBeCalledWith(
'https://conduit.productionready.io/api/users/login',
{ user: { email, password } },
);
});
});
});
Notice that you could greatly improve your login function by returning something and handling error with an Authentication Error. Your tests would be more significant :
errors.js
export class DefaultError extends Error {
static STATUS_CODE = 500; // You can change it, it depends how you use it
name = 'DefaultError';
constructor() {
super('Default error, add what you want');
}
}
export class AuthenticationError extends Error {
static STATUS_CODE = 401;
name = 'AuthenticationError';
constructor() {
super('Wrong credentials');
}
}
login.js
import { AuthenticationError, DefaultError } from './errors';
export const login = async (email, password) =>
axios
.post('https://conduit.productionready.io/api/users/login', {
user: {
email,
password,
},
})
.then(response => response.data)
.catch(error => {
// Handles the error how you want it
if (error.status === AuthenticationError.STATUS_CODE) {
throw new AuthenticationError();
}
throw new DefaultError();
});
login.spec.js
import { login } from './login';
import { AuthenticationError, DefaultError } from './errors';
// Mock axios, else you will really request the endpoint
jest.mock('axios');
import axios from 'axios';
describe('Login tests', () => {
describe('login function', () => {
const email = 'test#test.com';
const password = 'password';
describe('with success', () => {
const data = { something: {} };
beforeEach(() => {
axios.post.mockResolvedValue({ data });
});
it('should call endpoint with given email & password', async () => {
await login(email, password);
expect(axios.post).toBeCalledWith(
'https://conduit.productionready.io/api/users/login',
{ user: { email, password } },
);
});
it('should return response data', async () => {
const response = await login(email, password);
expect(response).toStrictEqual(data);
});
});
describe('with error', () => {
describe('status 401', () => {
beforeEach(() => {
axios.post.mockRejectedValue({ status: 401 });
});
it('should throw AuthenticationError', async () => {
await expect(login(email, password)).rejects.toThrow(AuthenticationError);
});
});
describe('other status', () => {
beforeEach(() => {
axios.post.mockRejectedValue({});
});
it('should throw DefaultError', async () => {
await expect(login(email, password)).rejects.toThrow(DefaultError);
});
});
});
});
});
We could go further but I think you got the point. Btw, you don't need to split the tests as I did, I just enjoy being able to group the describe by the mocks needed and making little & readable tests.

express router test with multiple handlers

I am testing my guard middleware, but altough everything seems to be working fine my expect statement fails.
/// auth.test.js
const request = require('supertest');
const express = require('express');
const app = require('../../app');
const authMiddleware = require('./auth.middleware');
const mockRes = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.sendStatus = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
return res;
};
describe('Authorization', () => {
const guardedRouter = express.Router();
guardedRouter.get(
'/guardedandauthenticated',
[authMiddleware.authenticate, authMiddleware.authorize('admin')],
(req, res, _next) => {
console.log('seems to be working');
res.status(200);
console.log('res is 200000000');
},
);
let accessToken = '';
beforeAll(async () => {
const res = await request(app).post('/auth/login').send({
username: 'admin',
password: 'admin',
});
expect(res.status).toBe(200);
accessToken = res.body.accessToken;
});
it('should allow access to authorized roles', () => {
const response = mockRes();
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
response,
);
// THIS EXPECTATION IS FAILED
expect(response.status).toHaveBeenCalledWith(200);
});
});
/// auth.middleware.js
module.exports.authorize = role => {
return async (req, res, next) => {
if (!req.user) {
return res.status(403).send({
message: 'Unauthorized! No token provided!',
});
}
if (req.user.role === undefined) {
const privileges = await userService.getUserPrivileges(req.user.id);
req.user.role = privileges.map(f => f.privilege_name);
}
const userRoles = req.user.role;
const rolesToCheck = Array.isArray(role) ? role : [role];
if (!rolesToCheck.every(r => userRoles.includes(r))) {
return res.status(403).send({
message: `Unauthorized! Required privileges are: ${userRoles.toString()}`,
});
}
return next();
};
};
/// jest outcome
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 200
Number of calls: 0
I cleaned up the code, my similar assertions are successfull, and the code seems to be working fine, either the way I setup router is incorrect, or, actually I have no clue. Console messages in the router are on the jest output, so it works fine.
Thanks in Advance,
well it turned out to be a jest issue, you need to tell jest that you are done.
it('should allow access to authorized roles', async done => {
const res = { statusCode: 100 };
res.status = function (code) {
res.statusCode = code;
return res;
};
// #ts-ignore
guardedRouter.handle(
{
headers: { authorization: `Bearer ${accessToken}` },
url: '/guardedandauthenticated',
method: 'GET',
},
res,
);
setTimeout(() => {
done();
expect(res.statusCode).toBe(200);
}, 300);
});
so I added a done callback to test case, and checked value after the handler is done. This still does not look like an ideal solution. The thing is that, handle will call 3 functions, one of them is async, I could not get it to report correct without setting a timer. There should be a solution without the timer, can anyone help with that?

How to mock an async action creator in redux with jest

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'
});
}

Categories

Resources