Mock multiple api call inside one function using Moxios - jestjs

I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}

Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});

Related

how to mock react-query useQuery in jest

I'm trying to mock out axios that is inside an async function that is being wrapped in useQuery:
import { useQuery, QueryKey } from 'react-query'
export const fetchWithAxios = async () => {
...
...
...
const response = await someAxiosCall()
...
return data
}
export const useFetchWithQuery = () => useQuery(key, fetchWithAxios, {
refetchInterval: false,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
retry: 1,
})
and I want to use moxios
moxios.stubRequest('/some-url', {
status: 200,
response: fakeInputData,
})
useFetchWithQuery()
moxios.wait(function () {
done()
})
but I'm getting all sorts of issues with missing context, store, etc which I'm iterested in mocking out completely.
Don't mock useQuery, mock Axios!
The pattern you should follow in order to test your usages of useQuery should look something like this:
const fetchWithAxios = (axios, ...parameters) => {
const data = axios.someAxiosCall(parameters);
return data;
}
export const useFetchWithQuery = (...parameters) => {
const axios = useAxios();
return useQuery(key, fetchWithAxios(axios, ...parameters), {
// options
})
}
Where does useAxios come from? You need to write a context to pass an axios instance through the application.
This will allow your tests to look something like this in the end:
const { result, waitFor, waitForNextUpdate } = renderHook(() => useFetchWithQuery(..., {
wrapper: makeWrapper(withQueryClient, withAxios(mockedAxios)),
});
await waitFor(() => expect(result.current.isFetching).toBeFalsy());

Why am I only getting Mailgun.js error in Cloud Run?

I'm trying to send an email using Mailgun's npm client - Mailgun.js.
When sending in development mode, everything works correctly. But when I upload the Node server to Cloud Run, something breaks.
Here is the code in the sendEmail helper file:
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const sendEmail = async ({ to, subject, text, attachment, scheduledDate }) => {
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: 'api',
key: process.env.MAILGUN_KEY,
url: 'https://api.eu.mailgun.net'
});
const data = {
from: `<myemail#mydomain.com>`,
to,
subject,
text
};
if (attachment) {
data.attachment = attachment;
}
if (scheduledDate) {
data['o:deliverytime'] = new Date(scheduledDate).toUTCString();
}
try {
const result = await mg.messages.create(process.env.MAILGUN_DOMAIN, data);
if (result.status && result.status !== 200) {
throw ({ code: result.status, message: result.message });
}
return true;
} catch(err) {
console.log(err);
return { error: err };
}
};
export default sendEmail;
And then in another file:
import { Router } from 'express';
import generateInvoicePDF from '../helpers/generateInvoicePDF.js';
import sendEmail from '../helpers/sendEmail.js';
const router = Router();
router.post('/email', async (req, res, next) => {
try {
const file = await generateInvoicePDF(invoice);
if (file?.error) {
throw ({ code: pdf.error.code, message: pdf.error.message });
}
const email = await sendEmail({
to: 'testemail#example.com',
subject: 'Invoice',
text: 'Test',
attachment: { filename: 'Invoice', data: file }
});
if (email?.error) {
throw ({ code: email.error.code, message: email.error.message });
}
res.status(200).json({ success: true });
} catch(err) {
next(err);
}
});
export default router;
The error I get when in production mode in Cloud Run's logs is:
TypeError: fetch failed
at Object.processResponse (node:internal/deps/undici/undici:5575:34)
at node:internal/deps/undici/undici:5901:42
at node:internal/process/task_queues:140:7
at AsyncResource.runInAsyncScope (node:async_hooks:202:9)
at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8) {
cause: TypeError: object2 is not iterable
at action (node:internal/deps/undici/undici:1661:39)
at action.next (<anonymous>)
at Object.pull (node:internal/deps/undici/undici:1709:52)
at ensureIsPromise (node:internal/webstreams/util:172:19)
at readableStreamDefaultControllerCallPullIfNeeded (node:internal/webstreams/readablestream:1884:5)
at node:internal/webstreams/readablestream:1974:7
}
Why the hell does it work in development mode on my local machine, but not when uploaded to Cloud Run?
For anyone struggling with something similar - I eventually figured out the problem.
On my local machine, where everything was working as expected, I'm using Node v16.15.0, whereas in the Dockerfile, I had specified
FROM node:latest
and therefore Cloud Run was using a newer version, which led to the problems...
I've now deployed using version 16.15.0 and everything works fine

Implementing retry logic in node-fetch api call

I hope you will forgive the beginners question, I'm trying to implement a simple retry policy for an api call using node-fetch
This is being added to an existing repo using TypeScript so I'm using this also, hence the data definitions.
async checkStatus(custId: string, expectedStatus: string) {
const response = await fetch(
`${'env.API_URL'}/api/customer/applications/${custId}`,
{
method: 'GET',
headers: headers,
},
)
expect(response.status, "Response status should be 200").to.be.equal(200)
const resp = await response.json()
expect(resp.status).to.contain(expectedStatus)
return resp.status;
}
I am calling it like so
await this.checkApplicationStatus(custId, 'NEW')
await this.checkApplicationStatus(custId, 'EXISTING')//and so forth
Is there a neat way of retrying based on an unexpected expectedStatus ?
Again, I appreciate there may be many examples out there but as a beginner, I am struggling to see a good/best-practice approach so looking for someone to provide an example. I don't need to use Chai assertions, this was just my first attempt.
TIA
you can check a library that I've published #teneff/with-retry
to use it you'll have to define error classes like these:
class UnexpectedStatus extends Error {
constructor(readonly status: number) {
super("Unexpected status returned from API");
}
}
class ResourceNotFound extends Error {
constructor() {
super("Resource not found in API");
}
}
and throw respectively depending on the status code:
export const getCustomerApplications = async ( custId, headers ) => {
const result = await fetch(`https://example.com/api/customer/applications/${custId}`, {
method: "GET",
headers,
});
if (result.status === 404) {
throw new ResourceNotFound();
} else if (result.status > 200) {
throw new UnexpectedStatus(result.status);
}
return result;
};
and then just use the HOF from the library to wrap your function, with options (how many retries should be attempted and for which errors should it be retrying)
const retryWhenReourceNotFoundOrUnexpectedStatus = withRetry({
errors: [UnexpectedStatus, ResourceNotFound],
maxCalls: 3,
});
const getCustomerApplicationsWithRetry =
retryWhenReourceNotFoundOrUnexpectedStatus(getCustomerApplications);
const result = await getCustomerApplicationsWithRetry(1234, {
Authorization: "Bearer mockToken",
});
Check this working setup

How to test files and json data at the same time with jest?

I have a post request with express that upload a file and some data to the mongodb:
// Routes
Router.post('/api/training', validator(createVideoSchema, 'body'), uploadVideo, createVideoHandler);
// Route Handlers
async function createVideoHandler (req: Request, res: Response, next: NextFunction) {
try {
const dataToCreate = {
...req.body,
url: req.file?.path,
mimetype: req.file?.mimetype
};
const data = await service.create(dataToCreate);
response(req, res, data, 201);
} catch (error) {
next(error);
}
}
the body must be validate by joi using the following schema:
import Joi from 'joi';
const title = Joi.string().email().min(5).max(255);
const description = Joi.string().min(5).max(255);
const thumbnail = Joi.string().min(5).max(255);
const tags = Joi.array().items(Joi.string().min(5).max(100));
const createVideoSchema = Joi.object({
title: title.required(),
description: description.required(),
thumbnail: thumbnail.required(),
tags: tags.required(),
});
export { createVideoSchema };
Then I am creating a test to verify I am receiving a 201 status code:
it('should have a 201 status code', async () => {
const response = await request(app).post(route)
.set('Accept', 'application/json')
.field('title', data.title)
.field('description', data.description)
.field('thumbnail', data.thumbnail)
.field('tags', data.tags)
.attach('video', Buffer.from('video'), { filename: 'video.mp4' });
expect(response.status).toBe(201);
});
For some reason the validation middleware throws me a 400 error saying that the data is missing:
Error: "title" is required. "description" is required. "thumbnail" is required. "tags" is required
I tried to send the data using .set('Accept', 'multipart/form-data') but it throws me the same error.
I guess this error has to do with the way I send the data, but I don't fully understand.
You typically should not call a live API from a test. Instead you should mock the different possibly API response scenarios and be sure your code handles the different possibilities correctly. Ideally you'll also have a client class of some kind to place direct calls to your API inside a class that can easily be mocked.
For example, you could mock the endpoint response for valid data with something like:
export class VideoClient {
async createVideo(data) {
const response = await request(app).post(route) // Whatever url points to your API endpoint
.set('Accept', 'application/json')
.field('title', data.title)
.field('description', data.description)
.field('thumbnail', data.thumbnail)
.field('tags', data.tags)
.attach('video', Buffer.from('video'), { filename: 'video.mp4' });
if (response.status.ok) {
return { response, message: 'someGoodResponseMessage'};
}
return { response, message: 'someErrorOccurred' };
}
}
Then in your test you can mock your client call:
import { VideoClient } from './clients/VideoClient.js'; // or whatever path you saved your client to
const goodData = { someValidData: 'test' };
const badData = {someBadData: 'test' };
const goodResponse = {
response: { status: 201 },
message: 'someGoodResponseMessage'
}
const badResponse = {
response: { status: 400 },
message: 'someErrorOccurred'
}
it('should have a 201 status code', async () => {
VideoClient.createVideo = jest.fn().mockReturnValue(goodResponse);
const results = await VideoClient.createVideo(goodData);
expect(results.response.status).toBe(201);
expect(results.message).toEqual('someGoodResponseMessage');
});
it('should have a 400 status code', async () => {
VideoClient.createVideo = jest.fn().mockReturnValue(badResponse);
const results = await VideoClient.createVideo(badData);
expect(results.response.status).toBe(400);
expect(results.message).toEqual('someErrorOccurred');
});
This is by no means a working test or exhaustive example, but demonstrating the idea that you really should not call your API in your tests, but instead call mock implementations of your API to handle how your client code responds in different situations.

Unit Testing Express with mocha/chai/sinon - how do I test my res.send object shape?

I am unit testing the individual components that lead up to an API response, in other words, I'm testing it independently of a route since every route runs through this component
I need to test that the function responsible for sending my express response is the correct shape, but without sending an actual HTTP request I can't figure out how to test it.
Here is my component
'use strict'
const moment = require('moment')
module.exports = (req, res, payload) => {
try {
let data = []
if (payload.token) data.push({ token: payload.token })
data.push({ [payload.resource]: payload.data })
res.send({
status: 'OK',
recordCount: payload.data.length,
startTimestamp: req.start.toDate(),
endTimestamp: moment().toDate(),
timeTaken: moment().toDate().getTime() - req.start.toDate().getTime(),
data: data
})
} catch (error) {
return res.status(500).json({
errors: [{
location: 'n/a',
param: 'n/a',
msg: 'something happened when generating the response'
}]
})
}
}
here is my current test ...
const chai = require('chai')
const sinonChai = require('sinon-chai')
const { mockReq, mockRes } = require('sinon-express-mock')
const moment = require('moment')
const present = require('../../src/lib/present')
chai.use(sinonChai)
describe('unit test the present lib method', () => {
it('should return the expected shape', (done) => {
const req = mockReq({
start: moment().toDate(),
body: {}
})
const res = mockRes()
const shape = present(req, res, {
resource: 'empty_array',
data: []
})
shape.should.have.own.property('data') // doesnt work
// AssertionError: expected { Object (append, attachement, ...) } to have own property 'data'
done()
})
})
To properly test schema of response you need to do E2E test, which requires you, to send an API call.
If you want to test just logic, inside of the route, you can always extract it to some service, and just test this service.
You can read the following article: https://www.freecodecamp.org/news/how-to-mock-requests-for-unit-testing-in-node-bb5d7865814a/

Resources