I'm trying to follow run a cypress test with next.js and nock. Based on other examples and following the video, I tried to mock a simple GET request. However, my test fails on the cy.request as it makes an actual call instead of the mock call.
index.js
const nock = require('nock');
const http = require('http');
const next = require('next');
const injectDevServer = require('#cypress/react/plugins/next');
// start the Next.js server when Cypress starts
module.exports = async (on, config) => {
if (process.env.CUSTOM_SERVER == 'false') {
injectDevServer(on, config);
} else {
await startCustomServer(on, config);
}
return config;
};
async function startCustomServer(on, config) {
config.supportFile = false;
const app = next({ dev: true });
const handleNextRequests = app.getRequestHandler();
await app.prepare();
const customServer = new http.Server(async (req, res) => {
return handleNextRequests(req, res);
});
await new Promise((resolve, reject) => {
customServer.listen(3000, (err) => {
if (err) {
return reject(err);
}
console.log('> Ready on http://localhost:3000');
resolve();
});
});
// register handlers for cy.task command
on('task', {
clearNock() {
nock.restore();
nock.cleanAll();
return null;
},
async nock({ hostname, method, path, statusCode, body }) {
nock.activate();
console.log(
'nock will: %s %s%s respond with %d %o',
method,
hostname,
path,
statusCode,
body
);
// add one-time network stub like
method = method.toLowerCase();
nock(hostname)[method](path).reply(statusCode, body);
return null;
},
});
}
my-test.spec.js
describe('my-test', () => {
beforeEach(() => {
cy.task('clearNock');
})
it('execute', () => {
cy.task('nock', {
hostname: 'https://localhost:3000',
method: 'GET',
path: '/myapi/api/layout',
statusCode: 200,
body: {
id: 'NmbFtH69hFd',
status: 200,
success: true
}
})
cy.request(
'https://localhost:3000/myapi/api/layout/'
).as('API'); // <-- Fails here with error
cy.get('API').then((response) => {
assert.exists(response.success);
});
});
});
Related
nestjs controller.ts
#Patch(':id')
async updateProduct(
#Param('id') addrId: string,
#Body('billingAddr') addrBilling: boolean,
#Body('shippingAddr') addrShipping: boolean,
) {
await this.addrService.updateProduct(addrId, addrBilling, addrShipping);
return null;
}
nestjs service.ts
async updateProduct(
addressId: string,
addrBilling: boolean,
addrShipping: boolean,
) {
const updatedProduct = await this.findAddress(addressId);
if (addrBilling) {
updatedProduct.billingAddr = addrBilling;
}
if (addrShipping) {
updatedProduct.shippingAddr = addrShipping;
}
updatedProduct.save();
}
there is no problem here. I can patch in localhost:8000/address/addressid in postman and change billingAddr to true or false.the backend is working properly.
how can i call react with axios?
page.js
const ChangeBillingAddress = async (param,param2) => {
try {
await authService.setBilling(param,param2).then(
() => {
window.location.reload();
},
(error) => {
console.log(error);
}
);
}
catch (err) {
console.log(err);
}
}
return....
<Button size='sm' variant={data.billingAddr === true ? ("outline-secondary") : ("info")} onClick={() => ChangeBillingAddress (data._id,data.billingAddr)}>
auth.service.js
const setBilling = async (param,param2) => {
let adressid = `${param}`;
const url = `http://localhost:8001/address/`+ adressid ;
return axios.patch(url,param, param2).then((response) => {
if (response.data.token) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
})
}
I have to make sure the parameters are the billlingddress field and change it to true.
I can't make any changes when react button click
Since patch method is working fine in postman, and server is also working fine, here's a tip for frontend debugging
Hard code url id and replace param with hard coded values too:
const setBilling = async (param,param2) => {
// let adressid = `${param}`;
const url = `http://localhost:8001/address/123`; // hard code a addressid
return axios.patch(url,param, param2).then((response) => { // hard code params too
console.log(response); // see console result
if (response.data.token) {
// localStorage.setItem("user", JSON.stringify(response.data));
}
// return response.data;
})
}
now it worked correctly
#Patch('/:id')
async updateProduct(
#Param('id') addrId: string,
#Body('billingAddr') addrBilling: boolean,
) {
await this.addrService.updateProduct(addrId, addrBilling);
return null;
}
const ChangeBillingAddress = async (param) => {
try {
await authService.setBilling(param,true).then(
() => {
window.location.reload();
},
(error) => {
console.log(error);
}
);
}
catch (err) {
console.log(err);
}
}
const setBilling= async (param,param2) => {
let id = `${param}`;
const url = `http://localhost:8001/address/`+ id;
return axios.patch(url,{billingAddr: param2}).then((response) => {
if (response.data.token) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
})
}
so when i send a request which looks like that:
everythig in this api:
router.post('', async (req, res) => {
try {
if(!req.files || !req.body.format) {
res.send({
status: false,
message: 'No file or format'
});
} else {
let uuidv4 = () =>{
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
let video = req.files.video;
let video_name = uuidv4()
let video_format = req.body.format
if (allowedFormats.includes(video_format)) {
let oldVideoPath = './public/uploads/' + video_name + "." + video_format
const newVideoPath = './public/converted/' + video_name + ".mp3"
let video_path = oldVideoPath
video.mv(oldVideoPath)
let proc = new ffmpeg({source: video_path, nolog: true})
proc.setFfmpegPath("./ffmpeg/bin/ffmpeg.exe")
proc
.toFormat('mp3')
.on('end', function () {
res.send({
status: true,
message: 'File has been uploaded',
file: newVideoPath.substring(1)
});
})
.on('error', function (err) {
res.send({
status: false,
message: 'An error occurred ' + err,
});
})
.saveToFile(newVideoPath)
} else {
res.send({
status: false,
message: 'Wrong format!',
})
}
}
} catch (err) {
res.status(500).send(err);
}
});
works perfectly, but the second i send it from react
const fileHandler = (file) => {
const data = new FormData()
data.append('file', file)
data.append('format', baseFormat)
fetch(process.env.REACT_APP_API_IP+'/upload-video', {
method: 'POST',
body: data
})
.then(response => response.json())
.then(data => console.log(data))
}
it gives me an 500 (Internal Server Error).
I checked and when sent from react the file and format reach the api but it breaks somewhere after the uuidv4 function.
Any help appreciated!
You should specify that it is form data.
Add to your fetch
headers: { 'Content-Type': 'multipart/form-data' },
Other issue is that express does not handle multipart/form-data by itself. You have to use some middleware like multer - https://github.com/expressjs/multer
Express part:
const multer = require('multer');
const upload = multer({ dest: "uploads/" });
app.post("/upload-video", upload.single("video"), (req, res) => {
let video = req.file
// rest of your code
}
And in you react code remember to use video field name:
const fileHandler = (file) => {
const data = new FormData()
data.append('video', file)
// ...
I am stuck on this problem for 2 days. I am sending POSTrequest from frontend to the backend (and other GET requests too but the problem is only with POST). However, when my data goes to the backend it does not post anything to the rest api even though response is 200 OK. That's why when in response it should have given the posted data, it can't find it and gives null. This is my POST code in backend index.js:
const { response, request } = require('express');
require('dotenv').config()
const express = require('express');
const morgan = require('morgan');
const Contact = require('./models/contact.cjs');
const cors = require('cors')
const app = express();
app.use(express.json())
app.use(express.static('build'))
app.use(cors())
morgan.token('body', req => {
return JSON.stringify(req.body)
})
app.use(morgan(':method :url :status :res[content-length] - :response-time ms :body'));
const generateId = () => {
const randNum = Math.floor(Math.random() * 5000)
return randNum;
}
app.post('/api/persons', (req, res) => {
const body = req.body
console.log(body)
if (!body.name || !body.number) {
return res.status(400).json({
error: "missing data"
})
} else if (Contact.find({name: body.name})) {
Contact.findOneAndUpdate({name: body.name}, {$set: {number: body.number}}, {new:true})
.then(updatedContacts =>
res.json(updatedContacts)
)
.catch(err => console.log(err))
} else {
const contact = Contact({
id: generateId(),
name: body.name,
number: body.number,
date: new Date()
})
contact.save()
.then(savedContact => {
console.log(savedContact)
res.json(savedContact)
})
.catch(err => {
console.log(err)
})
}
})
const PORT = process.env.PORT
app.listen(PORT, () => {
console.log(`Server is working on ${PORT}`)
})
and this is how my frontend sends data to backend: contacts.js:
const create = (newObject) => {
const readyToPost = {
method: 'post',
url: `${baseUrl}`,
data: newObject,
headers: {'Content-Type': 'application/json'},
json: true
}
const request = axios(readyToPost)
return request.then(response => {
console.log(response.data)
return response.data
})
.catch(err => {
console.log(err)
})
}
And this is my react app's frontend.
Any ideas about why my data becomes null?
Any help would be appreciated!
Due to the synchronous nature of your code, the condition Contact.find({name: body.name}) was always returning the Query object which is true due to which the else if block was getting executed even when there was no such document. After entering the else if block, since there was no match, so findOneAndUpdate() was returning null.
Use findOne() instead of find(). find() returns a cursor which is empty but true whereas findOne() returns the first document matched (if matched) or else it will return null (if not matched).
// index.js (Backend)
app.post("/api/persons", async (req, res) => {
const body = req.body;
if (!body.name || !body.number) {
return res.status(400).json({
error: "missing data",
});
}
// Using findOne() instead of find(). Returns null if record not found.
const existing = await Contact.findOne({ name: body.name });
if (existing) {
Contact.findOneAndUpdate(
{ name: body.name },
{ $set: { number: body.number } },
{ new: true }
)
.then((updatedContacts) => {
console.log(updatedContacts);
res.status(200).json(updatedContacts);
})
.catch((err) => console.log(err));
} else {
const contact = Contact({
id: generateId(),
name: body.name,
number: body.number,
date: new Date(),
});
contact
.save()
.then((savedContact) => {
console.log(savedContact);
res.status(201).json(savedContact);
})
.catch((err) => {
console.log(err);
});
}
});
I am trying to create a private messaging app. The socket connects at first but then when I try to emit any event from the client side, it shows that socket.connected property is false.
Please help me out.
Here's is my client side code, Please note that socket.on("users") part works correctly because all of it happens when the socket it connected. It means the connection part is happening correctly. After that whenever I try to call a function that emits a socket event, it shows that socket.connected property is false and doesnt do anything.
Any help would be appreciated.
var connectionOptions = {
transports: ["websocket"],
autoConnect: false,
};
socket = io("http://localhost:3001", connectionOptions);
socket.on("connection _error", (err) => {
if (err.message === "invalid username") {
console.log("ERROR");
}
});
socket.on("users", (users) => {
users.forEach((user) => {
user.self = user.userID === socket.id;
//initReactiveProperties(user);
});
socket.on("user connected", (user) => {
// TODO
setUsers((existingusers) => [...existingusers, user]);
console.log(user);
});
// put the current user first, and then sort by username
users = users.sort((a, b) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
//console.log(users);
});
socket.on("private message", ({ content, from }) => {
console.log(content);
});
useEffect(() => {
const username = localStorage.getItem("username");
console.log(username);
socket.auth = { username };
socket.connect();
}, []);
function SendMessage() {
socket.emit("test", "hello");
// selectedChatUser
console.log(socket.connected);
if (selectChatUser) {
socket.emit("private message", {
content: "hello there",
to: selectChatUser.userID,
});
console.log("Message Sent");
}
}
And here is my server side code:
const app = require("express")();
const httpServer = require("http").createServer(app);
const cors = require("cors");
app.use(cors());
const options = {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
};
const io = require("socket.io")(httpServer, options);
io.use((socket, next) => {
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid usernmae"));
}
socket.username = username;
next();
});
io.on("connect", (socket) => {
console.log("New connection");
const users = [];
for (let [id, socket] of io.of("/").sockets) {
users.push({
userID: id,
username: socket.username,
});
}
socket.broadcast.emit("user connected", {
userID: socket.id,
username: socket.username,
});
socket.emit("users", users);
socket.on("test", () => {
console.log("test");
});
socket.on("private message", ({ content, to }) => {
console.log(content);
console.log("hello there");
socket.to(to).emit("private message", {
content,
from: socket.id,
});
});
});
httpServer.listen(3001, () => {
console.log("Server has started");
});
// https://socket.io/
Following line will re-run every time your component renders, losing reference to the socket that was actually connected:
socket = io("http://localhost:3001", connectionOptions);
You can use a ref to persist it between renders:
const socketRef = useRef();
socketRef.current = socket;
// use socketRef.current everywhere else in your code
I'm testing a component that calls an API to populate a table with data. Though axios is used, axios is being wrapped in a convenience method of sorts to populate headers before executing the request via interceptors. I've tried axios-mock-adapter, but it's not working. I'm still new to testing React and I'm lost on how to mock data coming back from the api/axios. How do I go about mocking the api call to mock the data for my tests to pass??
This is my simple test:
test('<EmailTable/> ', async () => {
const { debug, getByText } = render(<CommunicationEmail />);
await waitFor(() => expect(getByText('Test Email Subject')).toBeTruthy());
}
This is the axios wrapper (api.js):
const instance = axios.create({
baseURL: `${apiUrl}/v1`,
timeout: 12000,
withCredentials: true,
headers: headers,
});
//intercept requests to validate hashed auth token
instance.interceptors.request.use((request) => {
const token = request.headers['X-Our-Access-Token'];
if (
localStorage.getItem('user_token') == null ||
SHA256(token).toString(enc.Hex) == localStorage.getItem('user_token')
) {
return request;
} else {
console.log({ what: 'Auth key invalid' });
return Promise.reject('Invalid token!');
}
});
//intercept responses to handle 401 errors
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// handle 401 authentication errors and redirect to SSO
if (error.response != null && error.response.status != null && error.response.status === 401) {
console.error({ what: 'Authorization error', e: error });
}
return Promise.reject(error);
}
);
export default instance;
And here's a simplification of the component I'm trying to test:
import api from './api.js';
const EmailTable = () => {
const [emails, setEmails] = useState();
useEffect(() => {
if(!emails) {
getEmails();
}
}, [emails]);
const getEmails = async () => {
await api({
method: 'GET',
url: `/communications/emails`,
}).then((response) => {
if (response.success) {
setEmails(response.emails);
}
}
}
if(!emails) { return <div> Loading... </div> };
return <div>{emails}</div>;
}
UPDATE WITH SOLUTION:
To mock the axios wrapper that is my API, I had to mock the api module and return a resolved promise like so:
jest.mock('../api', () => {
return function (request) {
// If we want to mock out responses to multiple API requests, we could do if (request.url = "/blah/blah") { return new Promise.... }
return new Promise((resolve) => {
resolve({
data: { success: true, emails: [] },
});
});
};
});