fetchMock function makes actual API calls instead of mocking the requests - reactjs

I am trying to test my signupAction shown below.
import axios from 'axios';
import actionTypes from '../action_types';
import { apiRequest } from '../common_dispatch';
export const signupAction = (user) => async (dispatch) => {
dispatch(apiRequest(true));
await axios
.post(`${process.env.REACT_APP_API_URL}/users`, { ...user }, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
dispatch(
{
type: actionTypes.REGISTER_SUCCESS,
payload: response.data.user,
},
);
dispatch(apiRequest(false));
})
.catch((error) => {
let errors = 'ERROR';
if (error.message === 'Network Error') {
errors = error.message;
} else {
errors = error.response.data.errors;
console.log(error);
}
dispatch(
{
type: actionTypes.REGISTER_FAIL,
payload: errors,
},
);
dispatch(apiRequest(false));
});
};
I figured I could mock the API call above using the fetchMock library. The problem is fetchMock makes actual calls hence the test passes in the first intance but fails when I run it the second time because the user I am trying to sign up already exists. my test is as shown below.
mport configureMockStore from 'redux-mock-store';
import * as actions from './signup.action';
import mocks from './mocks';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('signUp actions', () => {
afterEach(() => {
fetchMock.resetMocks();
console.log('yess bro am called')
})
it('dispatches create REGISTER_FAIL when signup has been done', async () => {
fetchMock.postOnce('/users', { ...mocks.user }, {
headers: { 'Content-Type': 'application/json' },
});
const expectedActions = [
{ type: 'API_REQUEST', payload: true },
{ type: 'REGISTER_FAIL', payload: { email: "Email karanilarrygmail.com is not a valid email" } },
{ type: 'API_REQUEST', payload: false },
]
const store = mockStore(mocks.user);
return store.dispatch(actions.signupAction(mocks.user)).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
});
mocks.user is an object containing the user signup data.
What am I doing wrong

Related

Error Cannot set headers after they are sent to the client

I got this error whenever I try to log In using Google Login API
In my console I get this error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
and in the screen I get white page with this error:
InternalOAuthError: Failed to fetch user profile
I'm using two Login method, one is normal and one using Passport JS
Login.jsx Login Page
import { useEffect, useContext, useRef } from "react";
import { Context } from "../../context/Context";
import axios from "axios";
import { useState } from "react"
export default function Login() {
const userRef = useRef();
const passwordRef = useRef();
const { dispatch, isFetching } = useContext(Context);
const [error, setError] = useState(false);
// FOR LOGIN
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/login", {
email: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
}
};
// FOR GOOGLE LOGIN
useEffect(() => {
fetch(`http://localhost:4000/login/success`, {
method: 'GET',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true,
},
})
.then((response) => {
dispatch({ type: "LOGIN_START" });
if (response.status === 200) return response.json();
throw new Error('failed to authenticate user');
})
.then((responseJson) => {
dispatch({ type: "LOGIN_SUCCESS", payload: responseJson.data });
})
.catch((error) => {
dispatch({ type: "LOGIN_FAILURE" });
// eslint-disable-next-line no-console
console.error("Failed to authenticate user", error)
});
}, []);
const google = () => {
window.open("http://localhost:4000/auth/google/callback", "_self");
};
return()
}
auth.js Route:
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
error: false,
message: "succesfull",
user: req.user,
cookies: req.cookies
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});

Uploading file to a server via the next js api route

I am using the Next.js api as a middleware before transferring the requests to the server, I am trying to send a multipart/formdata request with a file, it works if I call the backend API directly from the client-side with a FormData object, I wrote the Next API to parse that form data, form a new form data (server side this time) and call the backend API but that fails.
Here is the code:
import axios from "axios";
import formidable from "formidable";
import FormData from "form-data";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
//
import BlogAPIs from "utils/apis/BlogAPIs";
export const config = {
api: {
bodyParser: false,
},
};
export default async (req: NextApiRequest, res: NextApiResponse) => {
try {
const session = await getSession({ req });
const formData = new FormData();
const fs = require("fs");
const data: { fields: any; files: any } = await new Promise(
(resolve, reject) => {
const form = new formidable.IncomingForm();
form.parse(req, (err: any, fields: any, files: any) => {
if (err) reject({ err });
resolve({ fields, files });
});
}
);
["title", "content", "description", "thumbnail"].map((key) => {
data.fields[key] && formData.append(key, data.fields[key]);
data.files[key] &&
formData.append(key, fs.createReadStream(data.files[key].filepath));
});
let config = {
method: "post",
url: `${process.env.API_BASE_URL}/blogs/`,
headers: {
Authorization: `Bearer ${session?.backendToken as string}`,
...formData.getHeaders(),
},
data: formData,
};
await axios(config);
res.status(200).json("Succesfully added blog");
} catch (error: any) {
res.status(700).json(error.message);
}
};
I can't seem to figure out what I am doing wrong here...
This is how I was able to achieve it:
import axios from "axios";
import formidable from "formidable";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
import { processError } from "utils/apis/processError";
//
export const config = {
api: {
bodyParser: false,
},
};
export default async (req: NextApiRequest, res: NextApiResponse) => {
try {
const FormData = require("form-data");
const concat = require("concat-stream");
const fs = require("fs");
const session = await getSession({ req });
const data: { fields: any; files: any } = await new Promise(
(resolve, reject) => {
const form = new formidable.IncomingForm({
keepExtensions: true,
});
form.parse(req, (err: any, fields: any, files: any) => {
if (err) reject({ err });
resolve({ fields, files });
});
}
);
const promise = new Promise<{ data: any; headers: any }>((resolve) => {
const formData = new FormData();
["title", "content", "description", "tags"].map((key) => {
data.fields[key] && formData.append(key, data.fields[key]);
});
data.files["thumbnail"] &&
formData.append(
"thumbnail",
fs.createReadStream(data.files["thumbnail"].filepath),
data.files["thumbnail"].originalFilename
);
formData.pipe(
concat({ encoding: "buffer" }, (data: any) =>
resolve({ data, headers: formData.getHeaders() })
)
);
});
promise
.then(({ data, headers }) =>
axios.post(`${process.env.API_BASE_URL}/blogs/`, data, {
headers: {
Authorization: `Bearer ${session?.backendToken as string}`,
...headers,
},
})
)
.catch((error) => {
const errorMessage = processError(error);
res.status(700).json(errorMessage);
})
.then((response) => {
res.status(200).json({
slug: response?.data.slug,
});
});
} catch (error: any) {
res.status(700).json(error.message);
}
};
I created a promise with the streaming data and then sent the same to my server.

Cookies are not authorized, we will not send any data. when trying to use Paystack inmy next app

I keep getting this message when I am trying to use payStack in Next.js, And I have looked for any possible means to solve this but I haven't seen the solution to it
const componentProps = {
email: userInfo.email,
amount: totalPrice * 100,
metadata: {
name: shippingAddress?.fullName,
},
publicKey,
text: "Pay Now",
onSuccess: async () => {
try {
dispatch({ type: "PAY_REQUEST" });
const { data } = await axios.put(
`/api/orders/${order._id}/pay`,
{
headers: {
authorization: `Bearer ${userInfo.token}`,
},
}
);
dispatch({ type: "PAY SUCESS", payload: data });
alert("Thanks for doing business with us! Come back soon!!");
} catch (error) {
alert(getError(error));
}
},
onClose: () => alert("Wait! Don't leave :("),
};
And the message on my console is "Cookies are not authorized, we will not send any data."
This is the endpoint
import axios from "axios";
import nc from "next-connect";
import { isAuth } from "../../../../lib/auth";
const handler = nc();
handler.use(isAuth);
handler.put(async (req, res) => {
const projectId = "projectId";
const dataset = "dataset";
const tokenWithAccess =token
await axios.post(
`https://${projectId}.api.sanity.io/v1/data/mutate/${dataset}`,
{
mutations: [
{
paths: {
id: req.query.id,
set: {
isPaid: true,
paidAt: new Date().toString(),
"paymentResult.id": req.body.id,
"paymentResult.status": req.body.email_address,
"paymentResult..email_address": req.body.id,
},
},
},
],
},
{
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${tokenWithAccess}`,
},
}
);
res.send({ message: "Order Successfully" });
});
export default handler;
Here is my endpoint for the order information

how to jest test an async action with axios in react?

I have an action-generator register.js:
import { REGISTER_SUCCESS, REGISTER_FAIL } from "./types";
import axios from "axios";
export const register = (formData) => async (dispatch) => {
const { name, email, password } = formData;
const configRegister = {
method: "post",
url: "/api/users",
headers: { "Content-Type": "application/json" },
data: { name, email, password },
};
try {
const res = await axios(configRegister);
const token = res.data.token;
dispatch({
type: REGISTER_SUCCESS,
payload: {
token,
isAuthenticated: true,
loading: false,
},
});
} catch (err) {
console.error(err);
dispatch({
type: REGISTER_FAIL,
payload: {
token: null,
isAuthenticated: false,
loading: true,
},
});
}
};
the endpoint /api/users returns {token:'a_token_string'} on being successful.
How should i test this action-generator using jest ?
I tried doing this, register.test.js :-
import {
REGISTER_SUCCESS,
} from "./types";
import thunk from "redux-thunk";
import configureMockStore from "redux-mock-store";
import axios from "axios";
jest.mock("axios");
/** mock-store */
const createMockStore = configureMockStore([thunk]);
const defaultState = [];
const store = createMockStore(defaultState);
/** reset mock */
afterEach(() => jest.resetAllMocks());
test("should register a user ", async () => {
axios.post.mockImplementation(() => {
return Promise.resolve({
status: 200,
body: {
token: "testToken",
},
});
});
const res = await axios.post("/api/users");
console.log(res.body);
const testUser = {
name: "testName",
email: "test#email.com",
password: "testPassword",
};
await store.dispatch(register(testUser)).then(() => {
expect(store.getActions()[0]).toEqual({
type: REGISTER_SUCCESS,
payload: {
token: "testToken",
isAuthenticated: true,
loading: false,
},
});
});
});
You're quite close to get it done. The thing is you're mocking axios.post while your implementation is using directly from axios object. As long as you mock axios object then it would work as it should. Here is proposed changes, please check inline comments for things you should also change:
test("should register a user ", async () => {
// Mock `axios` object directly
axios.mockImplementation(() => {
return Promise.resolve({
status: 200,
// should also change from `body` to `data` as well
data: {
token: "testToken",
},
});
});
// it will no longer work since the mock is changed
// const res = await axios.post("/api/users");
// console.log(res.body);
const testUser = {
name: "testName",
email: "test#email.com",
password: "testPassword",
};
await store.dispatch(register(testUser)).then(() => {
expect(store.getActions()[0]).toEqual({
type: REGISTER_SUCCESS,
payload: {
token: "testToken",
isAuthenticated: true,
loading: false,
},
});
});
});

Nock not working with axios get at actions async test

I am trying to test my async actions at redux but I am not getting it.
I am using nock and axios, so I am trying to receive a responde data from axios get to test my actions:
describe('Async Actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('should load transmissors', (done) => {
const localStorage = {
token: 'a9sd8f9asdfiasdf'
};
nock('https://tenant.contactto.care')
.get('/api/clients/1/transmissors/', {
reqheaders: { 'Authorization': "Token " + localStorage.token }
})
.reply(200, { data: [
{
"id": 12,
"zone": "013",
"client": 1,
"description": "pingente",
"identifier": "",
"general_info": ""
},
{
"id": 15,
"zone": "034",
"client": 1,
"description": "colar",
"identifier": "",
"general_info": ""
}
]});
axios.get(`/api/clients/1/transmissors/`, {
headers: { 'Authorization': "Token " + localStorage.token },
}).then(transmissors => {
console.log(transmissors);
}).catch(error => {
throw(error);
})
done();
});
});
and here is my action:
export function loadTransmissors(clientId){
return function(dispatch){
axios.get(`/api/clients/${clientId}/transmissors/`, {
headers: { 'Authorization': "Token " + localStorage.token },
}).then(transmissors => {
dispatch(loadTransmissorsSuccess(transmissors.data, clientId));
}).catch(error => {
throw(error);
})
}
}
But I receiving this error at console.log:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): SyntaxError
I found this answer from Dan Abramov:
How to unit test async Redux actions to mock ajax response
https://github.com/reactjs/redux/issues/1716
Does anyone know how to make a test with redux-thunk.withExtraArgument?
Thanks in advance.
I solved my problem injecting axios via argument at redux thunk
https://github.com/gaearon/redux-thunk#injecting-a-custom-argument
So I changed my redux thunk at my store:
applyMiddleware(thunk)
for
applyMiddleware(thunk.withExtraArgument({ axios }))
So I updated my async return functions at actions
From:
return (dispatch) => {
...
}
To:
return (dispatch, getState, { axios }) => {
...
}
at my actions.test I mocked an api with promises:
const axios = {
get: (url,params) => Promise.resolve({data: transmissors})
}
injected at redux thunk:
const middleware = [thunk.withExtraArgument({axios})];
const mockStore = configureMockStore(middleware);
function asyncActions () {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(transmissorsActions.loadTransmissors(1)))
}
}
and I used the function asyncActions to test my actions at my store:
it('should load transmissors', (done) => {
const expectedAction = { type: types.LOAD_TRANSMISSORS_SUCCESS, transmissors, clientId};
const store = mockStore({transmissors: [], expectedAction});
store.dispatch(asyncActions()).then(() => {
const action = store.getActions();
expect(action[0].type).equal(types.LOAD_TRANSMISSORS_SUCCESS);
expect(action[0].transmissors).eql(transmissors);
expect(action[0].clientId).equal(1);
});
done();
});
You can have more info about redux-mock-store with this sample:
https://github.com/arnaudbenard/redux-mock-store/blob/master/test/index.js

Resources