Testing an HTTP post Request with mocha using nock - reactjs

I'm learning how to test a frontend webapp without any connection to the API.
My problem is: I have to test an POST HTTP Request but always get an error : TypeError: loginUser(...).then is not a function.
I know my expect is not correct. I must change the data for a JWT token, and also don't know yet hot to do it.
It's a simple user authentication. Http post sending an email and password, getting back a JWT (json web token). I have to write a test to make sure I've send the correct information and get a JWT as response.
Thanks for your help
Here is my code:
//login.test.js
const expect = require('chai').expect;
const loginUser = require('../src/actions/authActions').loginUser;
const res = require('./response/loginResponse');
const nock = require('nock');
const userData = {
email: 'test#test.com',
password: '123456'
};
describe('Post loginUser', () => {
beforeEach(() => {
nock('http://localhost:3000')
.post('/api/users/login', userData )
.reply(200, res);
});
it('Post email/pwd to get a token', () => {
return loginUser(userData)
.then(res => {
//expect an object back
expect(typeof res).to.equal('object');
//Test result of name, company and location for the response
expect(res.email).to.equal('test#test.com')
expect(res.name).to.equal('Tralala!!!')
});
});
});
//authActions.js
import axios from "axios";
import setAuthToken from "../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import {
GET_ERRORS,
SET_CURRENT_USER,
USER_LOADING
} from "./types";
// Login - get user token
export const loginUser = userData => dispatch => {
axios
.post("/api/users/login", userData)
.then(res => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem("jwtToken", token);
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
// loginResponse.js
module.exports = { email: 'test#test.com',
password: '123456',
name: "Tralala!!!"
};
Actual result:
1) Post loginUser
Post email/pwd to get a token:
TypeError: loginUser(...).then is not a function
at Context.then (test/login.test.js:37:12)

The way you called loginUser method is not correct. This method returns another function. So, instead of loginUser(userData), you must also specify the dispatch parameter e.g. loginUser(userData)(dispatch).then().
I changed the method to specify return before axios statement
export const loginUser = userData => dispatch => {
return axios // adding return
.post("/api/users/login", userData)
.then(res => {
...
})
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
for test, I may involve Sinon to spy the dispatch
it("Post email/pwd to get a token", () => {
const dispatchSpy = sinon.spy();
return loginUser(userData)(dispatchSpy).then(res => {
//expect an object back
expect(typeof res).to.equal("object");
//Test result of name, company and location for the response
expect(res.email).to.equal("test#test.com");
expect(res.name).to.equal("Tralala!!!");
});
});
Hope it helps

Related

Why does my axios create function in React does not work?

I'm working on a fullstack app with Express and React. For the calls to the backend, I use axios (version 1.1.2). Before this version, I was using a function to avoid writing the same calls to the database every time. Now, I get this error:
POST http://localhost:5005/api/auth/signup 400 (Bad Request)
Where does it come from?
This is my non working code:
const API_URL = process.env.REACT_APP_API_URL
export default axios.create({
baseURL: `${API_URL}/api`,
timeout: 1000,
headers: {
"Content-type": "application/json",
},
})
// Here, http refers to the axios.create function
class AuthService {
signup(data: any) {
return http.post("/auth/signup", data)
}
}
const handleSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
authService
.signup(inputs)
...rest
}
But this is working:
const handleSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
axios
.post(`${API_URL}/api/auth/signup`, inputs)
...rest
}
Thanks for your help!
EDIT:
When I submit the form, on the front end I get this error, which on the back end would be returned if an input is empty:
Please provide your full name.
Back end code:
if (!fullName) {
return res
.status(400)
.json({ message: "Please provide your full name." })
}
EDIT 2:
I tried to add a console.log on the back end with the req.body and this is what I get:
{}
This is the full backend code:
router.post("/signup", (req, res, next) => {
const { email, fullName, password } = req.body
console.log(req.body)
if (!fullName) {
return res
.status(400)
.json({ message: "Please provide your full name." })
}
User.findOne({ email })
.then(foundUser => {
...rest
return User.create({
email,
fullName,
password,
}).then(createdUser => {
const payload = { user: createdUser }
const authToken = jwt.sign(
payload,
process.env.TOKEN_SECRET,
jwtConfig
)
res.status(201).json({
user: createdUser,
authToken: authToken,
})
})
})
.catch(err => console.log(err))
})
And my terminal returns this:
POST /api/auth/login 401 16.919 ms - 39
EDIT 3:
This is my Express app.js:
require("dotenv/config")
require("./db")
const express = require("express")
const app = express()
require("./config")(app)
const allRoutes = require("./routes/index")
app.use("/api", allRoutes)
require("./error-handling")(app)
module.exports = app
You can find the full repo here: https://github.com/JulSeb42/tsx-express-jwt

How to wait for a certain axios response before triggering redux-toolkit middleware? - react, socket.io, redux toolkit & express

tl;dr: I want to wait for the axios request response in App.tsx before the const socket = io() initialization in socketMiddleware.ts triggers
The authorization headers are received through an axios request as soon as the client loads the react app.
This axios request triggers as soon as the react app is loaded and refreshes after a certain time.
In App.tsx:
App.tsx
const silentRefresh = useCallback(async () => {
try {
const response: AxiosResponse = await axios.get(
(import.meta.env.VITE_BASEURL as string) + 'auth/refresh-token'
)
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${response.data.token}`
//set user
setTimeout(() => {
silentRefresh()
}, response.data.expiresIn * 1000 - 10000)
} catch (response: any) {
if (response.status !== 201) {
console.log('Not Authorized')
}
}
}, [dispatch])
useEffect(() => {
silentRefresh()
}, [silentRefresh])
This sets the authorization headers (if the client has the httpOnly cookie, to automatically log in) which authorizes the user for protected API endpoints of my express server, and refresh after a certain time.
I want to use this header as authorization token for the socket connection too.
In my redux-toolkit store.ts I added a middleware:
store.ts
const store = configureStore({
reducer: {/*reducers...*/},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(socketMiddleware()),
})
The socketMiddleware.ts looks like this, it tries to set the token from the headers but they are not received at this point:
socketMiddleware.ts
const socketMiddleware = () => {
const socket = io(import.meta.env.VITE_BASEURL as string, {
withCredentials: true,
auth: {
token: axios.defaults.headers.common['Authorization'],
},
})
return (store: any) => (next: any) => (action: any) => {
//some code...
next(action)
}
}
On my server.ts I check for the auth token, but it is undefined as the socket connection gets established before the auth headers are set on the client through the axios request in App.tsx
server.ts
io.use((socket, next) => {
try {
const token = socket.handshake.auth.token
if (!token) {
const error: any = new Error('No token sent, authorization denied')
error.statusCode = 401
next(error)
}
const decoded: any = jwt.verify(token, process.env.REFRESH_SECRET as string)
next()
} catch (error) {
console.log(error)
}
})
io.on('connection', (socket) => {
socket.emit('Hello from Server')
})
Thank you for your help.

Jest testing - how to handle JsonWebToken response

I am learning how to test my redux thunk actions and the response from my login includes a randomized JsonWebToken. I've written a variable called expectedActions that matches all the data coming back from the action except how to handle randomized strings (JWT). Any ideas on how to handle this?
-- Also, i need to pass real user information (usename/password) to get a LOGIN_SUCCESS response otherwise the function dispatches the LOGIN_FAIL action. Is that normal?
/* eslint-disable no-undef */
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
import * as actions from '../../../redux/actions/auth';
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
describe('redux async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('returns expected login response', async () => {
const userData = {
username: 'user',
email: 'user#gmail.com',
password: 'password',
};
const config = {
headers: {
'Content-Type': 'application/json',
},
};
fetchMock.getOnce('http://localhost:5000/api/v1/users', {
body: { ...userData },
config,
});
const expectedActions = { payload: { token: '' }, type: 'LOGIN_SUCCESS' };
// the value of the token above in the response is a randomized jwt string
const store = mockStore({});
return store
.dispatch(actions.login('user#gmail.com', 'password'))
.then(() => {
// return of async actions
const actionsResponse = store.getActions();
expect(actionsResponse[0]).toEqual(expectedActions);
});
});
});
Bonus: What is the point of fetchMock ? I borrowed the above code from another StackOverflow question and I have yet to understand what the fetchMock is doing.
I overrode the responses JWT with my own token of "123". I don't know if this is correct though, nor do i ever expect a response to this post.
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
describe('redux async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('returns expected login response', async () => {
const expectedActions = {
payload: { token: '123' },
type: 'LOGIN_SUCCESS',
};
const store = mockStore({ alert: [], auth: { token: '123' } });
return store
.dispatch(actions.login('user#gmail.com', 'somePassword'))
.then(() => {
// return of async actions
const actionsResponse = store.getActions();
actionsResponse[0].payload.token = '123';
expect(actionsResponse[0]).toEqual(expectedActions);
});
});
});

Property in state becomes undefined within action creators

I'm making an app where different users can add their own plants/flowers.
The flowerlist contains the users flowers and loads these items upon mounting.
class flowerList extends Component {
componentDidMount() {
this.props.getFlowers();
}
To send the correct GET request to the backend I need to have the currently logged in user's ID.
This is what the called action creator looks like:
export const getFlowers = () => (dispatch, getState) => {
dispatch(setFlowersLoading());
axios
.get(`/api/users/${getState().auth.user.id}/flowers`)
.then((res) =>
dispatch({
type : GET_FLOWERS,
payload : res.data
})
)
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
However, this doesn't work very well. It only works when coming directly from signing in. If I refresh the page, the app crashes with the error message "TypeError: Cannot read property 'id' of null". When writing the POST requests in a similar fashion it doesn't work well either, so I guess there must be a better way to access the state. I'd really appreciate any help in getting this to work.
When you login, you should set local storage to keep the users info something like these:
const setAuthorizationHeader = token => {
const Token = `Bearer ${token}`;
localStorage.setItem("Token", Token);
axios.defaults.headers.common["Authorization"] = Token;
};
you can add it to your user login action, after then(), when the login is successful, here is an example, I assume you handle the token in the backend, so after successful login, it sends the token with a respond(res.data):
export const loginUser = (userData) => dispatch => {
axios
.post("http://localhost:5000/api/users/login", userData)
.then(res => {
setAuthorizationHeader(res.data.token);
})
.catch(err => {
dispatch({
type: SET_ERRORS,
payload: err.response
});
});
};
Afterwards, put these to your app.js:
const token = localStorage.Token;
if (token) {
const decodedToken = jwtDecode(token);
if (decodedToken.exp * 1000 < Date.now()) {
store.dispatch(logoutUser());
window.location.href = "/login";
} else {
store.dispatch({ type: SET_AUTHENTICATED_USER });
axios.defaults.headers.common["Authorization"] = token;
store.dispatch(getUserData(decodedToken));
}
}
Here I used jwtDecode because I am using JWT to crypt my users' info and store it to the localStorage, these codes provide to look for Token in localStorage after refreshing the page. If the user logged in, there is the token and so the app will not crash

Axios-Redux in React to an Express endpoint-both .then and .catch triggered

I'm using a Redux Form to send a POST call to an Express endpoint. The endpoint is supposed to return the successfully saved object, or an error.
The endpoint successfully saves the posted data and returns the JSON. But Axios in the Redux action picks up both the .then and the .catch triggers-in the following action, it logs the following:
successful response: { …}
failure response: undefined
What am I doing wrong?
My Axios action:
export function addPlot(props) {
const user = JSON.parse(localStorage.getItem('user'));
return function(dispatch) {
axios
.post(
`${ROOT_URL}/plots`,
{
props
},
{ headers: { authorization: user.token } }
)
.then(response => {
console.log('successful response: ', response.data);
const plotModal = document.getElementById('plotModal');
plotModal.modal('dispose');
dispatch({ type: PLOT_ADDED, payload: response.data });
dispatch({ type: ADDING_PLOT, payload: false });
dispatch({
type: NEW_PLOT_GEOJSON,
payload: ''
});
})
.catch(response => {
console.log('failure response: ', response.data);
dispatch(authError(PLOT_ADD_FAILURE, 'Failed to add plot'));
});
}
My endpoint:
exports.newPlot = async (req, res, next) => {
console.log(JSON.stringify(req.body.props));
let company;
if (req.user.companyCode !== 'Trellis') {
company = req.user.companyCode;
} else {
company = req.body.props.company;
}
const {
name,
feature,
growerPhone,
plotCode,
rootStock,
region,
variety,
grower,
planted
} = req.body.props;
const plot = new Plot({
name,
grower,
variety,
planted,
region,
rootStock,
plotCode,
growerPhone,
feature,
company
});
try {
const newPlot = await plot.save();
res.json(newPlot);
} catch (e) {
console.log("couldn't save new plot", JSON.stringify(e));
return res.status(422).send({ error: { message: e, resend: true } });
}
};
You could use redux-thunk middleware to manage async actions.
The problem I see is that you are not dispatching the axios action, you must call dispatch(this.props.addPlot(props))in order to do something in the redux store.

Resources