"Expected signal to be an instanceof AbortSignal? with Jest Fetch Mock - jestjs

I am testing an rtk query mutation api call. I'm trying to match the mock result to a successful api call but I always get an error: 'Expected signal to be an instanceof AbortSignal'. I understand that this may be a side effect of jest-fetch-mock but am not sure how to convert the input(?) into a typeof AbortSignal.
test('successful response', async () => {
const wrapper = ({children}) => {
const storeRef = setupApiStore(testApi);
return <Provider store={storeRef.store}>{children}</Provider>;
};
const classState = {
statusCodes: 200,
message: 'Success',
details: {
overallPresent: 3,
overallTotal: 10,
classStrength: [
{className: 'class 1', present: 3, total: 3},
{className: 'class 2', present: 0, total: 7},
],
},
};
const body = {
schoolName: 'school 1',
className: 'class 1',
};
fetchMock.mockResponse(JSON.stringify(classState));
const {result, waitForNextUpdate} = renderHook(
() => useClassMutation(),
{wrapper},
);
const [students, initialResponse] = result.current;
expect(initialResponse.data).toBeUndefined();
expect(initialResponse.isLoading).toBe(false);
act(() => {
students(body);
});
await waitForNextUpdate({timeout: 500});
const loadedResponse = result.current;
console.log('loadedResponse: ', loadedResponse);
expect(loadedResponse).toEqual(paradeState); //error: 'Expected signal to be an instanceof AbortSignal'
});

This is not an RTK Query error - it's an error from the fetch polyfill you are using.
RTK Query is passing an AbortSignal into that fetch call - and then that fetch polyfill complains that AbortSignal is something incompatible.
=> if you polyfill global.fetch, you also have to polyfill global.AbortController with a compatible implementation.
How you do that is very different for each implementation of fetch, so you'll have to research that - but I hope this gives you a direction.

Related

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.

Stubbing pino logger as class instance

I have a custom log service, using a child instance of pinoLogger to log some things. Here is the code :
class LogService {
ihmLogger = loggerPino.child({});
async log(
req: Request,
level: number,
message: string,
additional?: string
): Promise<void> {
//Context initialisation
const logObj = this.initLogObj(
req,
ngxLoggerLevelToPinoLevel[level],
message,
additional
);
// Mapping log level with pino logger
switch (ngxLoggerLevelToPinoLevel[level]) {
case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.ERROR:
this.ihmLogger.error(logObj);
break;
case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.WARM:
this.ihmLogger.warn(logObj);
break;
case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.INFO:
this.ihmLogger.info(logObj);
break;
case CONSTANTS.DEFAULT.LOG_NGX_LEVEL.DEBUG:
this.ihmLogger.debug(logObj);
break;
}
}
private initLogObj(
req: Request,
level: number,
message: string,
additional?: string
): ILog {
const additionalObj = JSON.parse(additional || '{}');
return {...};
}
}
export default new LogService();
I'm trying to write unit-tests for this service. I'm using node-request-http to mock the Request object, successfully.
My problem concerns this child element. I'm simply trying to see if the correct function is called depending on the level, but I can't access it correctly. Here are the few syntaxes I tried :
[...]
import logService from './log.service';
import { loggerPino } from '../utils/logger';
[...]
it("test 1", async () => {
sinon.stub(loggerPino,'child').returns({
error: sinon.spy()
});
await logService.log(request, 5, 'msg', JSON.stringify({}));
console.log('A',logService.ihmLogger.error);
});
// >> A [Function: noop]
it("test 2", async () => {
const logger = sinon.stub(logService, 'ihmLogger').value({
error: sinon.spy()
});
await logService.log(request, 5, 'msg', JSON.stringify({}));
console.log('B',logger.error);
});
// >> B undefined
it("test 3", async () => {
const logger = sinon.stub(logService, 'ihmLogger').value({
error: sinon.spy()
});
await logService.log(request, 5, 'msg', JSON.stringify({}));
console.log('C',logService.ihmLogger.error);
});
// >> C [Function (anonymous)]
I never could access the "callCount" valut, let alone checking the given parameter with a getCall(0).args[0]
How should I write the stub to be able to correctly access the value i'm trying to test?
The solution here was to completely replace the ihmLogger property of the service, as done in the "B" try up there, but putting it in a variable and checking the variable, not the sinon.stub object returned :
it("Log error called", async () => {
const customLogger = {
trace: sinon.spy(),
debug: sinon.spy(),
info: sinon.spy(),
warn: sinon.spy(),
error: sinon.spy(),
fatal: sinon.spy()
};
sinon.stub(logService, 'ihmLogger').value(customLogger);
await logService.log(request, 5, 'msg', JSON.stringify({}));
expect(customLogger.error.callCount).to.eq(1);
expect(customLogger.fatal.callCount).to.eq(0); //double check to avoid false positive
});
I'm not unit-testing the pinoLogger object (I have to trust if to work as intended), I'm unit-testing the correct branch & call.
Doing this, I'm also able to inspect the logObj transmitted :
const builtObj = customLogger.error.getCall(0).args[0]

Mock multiple api call inside one function using Moxios

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

TypeError: Cannot read property 'address' of undefined

I need some help with testing node js code. I'm using mocha and supertest and I keep getting an error the same as the title on my test. I'm also using mongoose to connect to a mongo server and before I run the tests am adding in a few guest objects. I've been stumped on this for 2 days so any help is appreciated.
Here is the test error
0 passing (96ms)
1 failing
1) Guestss
GET /guests
should GET all the guests:
TypeError: Cannot read property 'address' of undefined
at Test.serverAddress (node_modules/supertest/lib/test.js:55:18)
at new Test (node_modules/supertest/lib/test.js:36:12)
at Object.get (node_modules/supertest/index.js:25:14)
at Context.<anonymous> (test/functional/api/guestTest.js:95:18)
Here is the test code
describe("GET /guests", () => {
it("should GET all the guests", done => {
console.log("before");
request(server)
.get("/guests")
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, res) => {
try {
expect(res.body).to.be.a("array");
expect(res.body.length).to.equal(2);
let result = _.map(res.body, guest => {
return {
name: guest.name,
people: guest.people,
roomno: guest.roomno,
breakfast: guest.breakfast,
roomtype: guest.roomtype,
check: guest.check
}
});
expect(result).to.deep.include({
name: "Tommy blue",
people: 5,
roomno: 0,
breakfast: true,
roomtype: "family",
check: "waiting"
});
expect(result).to.deep.include({
name: "Chad Warren",
people: 2,
roomno: 45,
breakfast: true,
roomtype: "double",
check: "in"
});
done()
} catch (e) {
done(e)
}
});
});
});
After looking into it the error messages were sending me on a wild goose chase and i just wasn't exporting my server.
Same here. My test had
const { app } = require('./app');
and my app had
module.exports = app;
I had to change the app.js line to have curly braces like the test has:
module.exports = { app };

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