testing login api with jest - javascript

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.

Related

sending push notification firebase react-js

I was working on a project using Firebase cloud messaging react. I was sending this to my server, but it doesn't work. Surely I have tried, but I don't know what's wrong again.
Below is the code.
Here it sends a POST request to Firebase, and it should send a notification to the user.
async function sendNotification(id, userMessage) {
const headers = {
'Authorization': `key=${code}`,
'Content-Type': 'application/json'
}
const message = {
'to': `${id}`,
'content_available': true,
'apns_priority': 5,
'notification': {
body: `${userMessage}`
},
const url = 'https://fcm.googleapis.com/fcm/send'
//console.log(code)
await axios.post(url, message, {
headers: headers
})
}
const sendMessageToServer = async (e) => {
//e.preventDefault();
toggle()
const docRe = doc(database, "help", mailer);
const data = {
email: user.email,
user: newMessage,
}
//console.log(data, 'not clear')
setNewMessage('')
//console.log(data, newMessage, 'cleared')
setShow(false)
if(newMessage === '') {
}
else {
const docRef = doc(database, "users", mailer);
await updateDoc(docRe, {
msg: arrayUnion(data)
})
.then(() => {
async function p() {
const id = await getDoc(docRef)
//console.log(id.data())
sendNotification(id.data().notice, `Admin : ${data.user}`)
}
p()
})
}
Sometimes it sends to my localhost because I tested there, but it doesn't work on my Netlify app. Secondly, I noticed that it keeps generating the same token for each user, but that's not the issue, but if you can help in both I would be grateful.
export default function Dashboard() {
async function callToken() {
await getToken(messaging, {vapidKey: process.env.NOTIFICATION})
.then((code) => {
//console.log(code)
async function docRef() {
const dc = doc(database, "users", auth.currentUser.email);
await updateDoc(dc, {
notice: code
});
}
docRef()
})
}
async function requestPermission() {
await Notification.requestPermission()
.then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.')
callToken()
}
})
}
const goTo = useNavigate();
useEffect(() => {
onAuthStateChanged(auth, (data) => {
if(!data) {
goTo('/login')
}
else {
currentBalance();
requestPermission()
}
})
})
}
Please know I imported all required modules.

next.js & next-auth When I send http request in getServerSideProps, getSession returns null in secured API Route

I am trying to secure the API Route and this API route is called in the Client and Server-side on different pages.
On the test page, it returns 401 error.
On the test2 page, it returns the content well.
I guess it doesn't pass session when I send the http request in the getServerSideProps.
My question is, how do I secure the API routes used on the client and server-side?
/pages/test
import React from 'react';
import axios from 'axios';
import { getSession } from 'next-auth/react';
const Test = (props) => {
return <div>test</div>;
};
export const getServerSideProps = async (context) => {
// it returns session data
const session = await getSession(context);
// it returns error
const res = await axios.get('/api/secret');
return {
props: {
session,
secret: res.data,
},
};
};
export default Test;
/pages/test2
import React, { useEffect } from 'react';
import axios from 'axios';
import { useSession, getSession } from 'next-auth/react';
const Test = (props) => {
const { data: session } = useSession();
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('/api/secret');
console.log(res.data);
};
fetchData();
}, [session]);
return <div>test</div>;
};
export default Test;
/pages/api/secret
import { getSession } from 'next-auth/react';
const handler = (req, res) => {
const { method } = req;
switch (method) {
case 'GET':
return getSomething(req, res);
default:
return res.status(405).json('Method not allowed');
}
};
const getSomething = async (req, res) => {
const session = await getSession({ req });
console.log(session);
if (session) {
res.send({
content: 'Welcome to the secret page',
});
} else {
res.status(401).send({
err: 'You need to be signed in.',
});
}
};
export default handler;
I found a solution.
export const getServerSideProps = async (ctx) => {
const session = await getSession(ctx);
const headers = ctx.req.headers;
if (session) {
const data = (
await axios.get(`${process.env.NEXTAUTH_URL}/api/secret`, {
headers: { Cookie: headers.cookie },
})
return {
props: {
data,
},
};
} else {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
};
/pages/api/secret
import { getSession } from 'next-auth/react';
const handler = async (req, res) => {
const { method } = req;
switch (method) {
case 'GET':
return await getSomething(req, res);
default:
return res.status(405).json('Method not allowed');
}
};
const getSomething = async (req, res) => {
const session = await getSession({ req });
// console.log(session);
if (session) {
res.send({
content: 'Welcome to the secret page',
});
} else {
res.status(401).send({
err: 'You need to be signed in.',
});
}
};
export default handler;
There is a specific method to handle request from serverSideProps, better than using useSession (which is meant for client requests)
https://next-auth.js.org/tutorials/securing-pages-and-api-routes#server-side
Best use unstable_getServerSession as mentioned in the documentation example
await unstable_getServerSession(req, res, authOptions)
with the authOptions coming as an export from your [...nextauth].js

Javascript await is a reserved word?

I think I am doing everything correctly. But the program is showing "await is a reserved keyword" in signin function. Here is the code:
import axios from "axios";
const signin = async (username, password) => {
try {
console.log("Sign in function called");
const res = await axios.post(`${process.env.baseURL}/auth/signin`, {
username,
password
});
console.log(res.data);
} catch (error) {
console.log(error);
}
};
const logout = () => {
// remove user from local storage to log user out
localStorage.removeItem("jobmanager_user");
};
export const userService = {
signin,
logout
};

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

unit-test with HTTP request returns Promise { <pending> }

I am using axios mock adapter to mock HTTP request to test my function. After I defined the behaviour for the function, and then I created an instance of the class to call the function, the result is
**Promise { <pending> }**,
what is the problem? how can I return the value I defined?
Here is my code:
UserService.js
export default class UserService {
getUserInfo = userId => {
const params = {
userId,
};
return axios
.get('https://www.usefortesting.com', {
params: { userId: params },
})
.then(response => response.data.userInfo)
.catch(error => error);
};
}
UserService.test.js
import React from 'react';
import axios from 'axios';
import UserService from './UserService';
import MockAdapter from 'axios-mock-adapter';
describe('testing', () => {
let axiosMock;
const Info = {
userInfo: {
id: '123',
name: 'omg',
},
};
beforeEach(function() {
axiosMock = new MockAdapter(axios);
});
afterEach(() => {
axiosMock.reset();
axiosMock.restore();
});
it('testing', () => {
axiosMock
.onGet('https://www.usefortesting.com', {
params: { userId: 'user_1' },
})
.reply(200, Info);
let userService = new UserService();
let response = userService.getUserInfo('user_1');
console.log(response);
});
});
You need to await for response in your test. Either use callbacks or async/await as shown below.
Your test should be like this:
it('testing', async () => { // notice async here
axiosMock
.onGet('https://www.usefortesting.com', {
params: { userId: 'user_1' },
})
.reply(200, Info);
let userService = new UserService();
let response = await userService.getUserInfo('user_1'); // notice await here
console.log(response);
});
OR
it('testing', () => {
...
userService.getUserInfo('user_1').then(response => {
console.log(response);
});
});
You can check this link on jest docs for more examples.
Also there is error in your getUserInfo() method, in params you are passing an object for userId but you need to pass string or int. What you should do is:
return axios.get('https://www.usefortesting.com', {
params: { userId: params.userId },
})...
OR
return axios.get('https://www.usefortesting.com', {
params,
})...

Categories

Resources