Node.js Express.js: mock request object for unit-test purpose - unit-testing

I have a method that check if request query string has some params:
checkMandatoryQueryParams (mandatoryQueryParams: String[], req: Request): void {
let result = true;
mandatoryQueryParams.forEach((element, idx) => {
if (!req.query.hasOwnProperty(element)) {
result = false;
}
});
return result;
}
in unit-testing i need to mock the request for test it, eg:
describe('Utils', () => {
it('checkMandatoryQueryParams', () => {
const req: Request = new Request(); // pseudo-code
req.query = "?foo=test&bar=test"; // pseudo-code
expect( checkMandatoryQueryParams(['foo', 'bar'], req) ).toEqual(true);
});
});
how can i mock the express request?

Two options:
Create your own mock object:
describe('Utils', () => {
/**
* Mocked Express Request object
*/
let mockedReq;
beforeEach(() => {
mockedReq = {
query: {}
};
})
it('checkMandatoryQueryParams', () => {
const req = mockedReq;
req.query = "?foo=test&bar=test"; // pseudo-code
expect(checkMandatoryQueryParams(['foo', 'bar'], req) ).toEqual(true);
});
});
Use manual mocks, similar to above:
describe('Utils', () => {
/**
* Mocked Express Request object
*/
let mockedReq;
beforeAll(() => {
mockedReq = jest.fn().mockImplementation(() => {
const req = {
// Implementation
};
return req;
});
});
beforeEach(() => mockedReq.mockReset())
it('checkMandatoryQueryParams', () => {
const req = mockedReq;
req.query = "?foo=test&bar=test"; // pseudo-code
expect(checkMandatoryQueryParams(['foo', 'bar'], req) ).toEqual(true);
});
});

Related

Pinia - How to mock a specific action to test a store

Using pinia, I am not able to test what seems to be a simple scenario.
Store
const useStore = defineStore('store', () => {
const id = ref('id');
const forceId = ref('forceId');
function shouldReturnForceId() {
return false;
}
function getId() {
if (shouldReturnForceId()) {
return forceId.value;
}
return id.value;
}
return {
id,
forceId,
shouldReturnForceId,
getId,
};
});
Helper
const createTestStore = (stubActions: boolean) => {
const pinia = createTestingPinia({
stubActions,
});
setActivePinia(pinia);
const store = useStore();
return store;
};
Test
describe('Pinia Test', () => {
describe('getId', () => {
// DOES NOT WORK - stubAction to false and mocking shouldReturnForceId
it('should return forceId if shouldReturnForceId is true', () => {
const store = createTestStore(false);
store.shouldReturnForceId = jest.fn(() => true);
expect(store.getId()).toEqual('forceId');
});
// DOES NOT WORK - stubAction to true and mocking shouldReturnForceId
it('should return forceId if shouldReturnForceId is true', () => {
const store = createTestStore(true);
store.shouldReturnForceId = jest.fn(() => true);
expect(store.getId()).toEqual('forceId');
});
// WORKS
it('should return id if shouldReturnForceId is false', () => {
const store = createTestStore(false);
expect(store.getId()).toEqual('id');
});
});
});
Question
How is it possible to test a store by only mocking one action among several?
Reading the documentation I get that we can stubActions: true, which will mock all actions, but this is not what I need.

Spying & mocking return values on ctors & methods in node module with Jest

After much trial/error, searching here on SO, & flexing my Google Fu, throwing in the towel & asking for help.
TL/DR -
Trying to correctly mock node module, change internal method return types, and spy on ctor's & method calls within the node module.
My specific scenario is to test the Microsoft Azure Storage blob SDK #azure/storage-blob, but the questions aren't specific to this package. It's just a good example as 4 LOC's capture achieve a task (upload a file to a storage container) as 2-3 of those LOC's cover 4 scenarios. Here's my code that I want to test, with comments on WHAT exactly I want to test:
export async function saveImage(account: string, container: string, imageBuffer: Buffer, imageName: string): Promise<void> {
try {
// init storage client
// (1) want to spy on args passed into ctor
const blobServiceClient: BlobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net`, new DefaultAzureCredential());
// init container
// (2) same as #1
const containerClient: ContainerClient = await blobServiceClient.getContainerClient(container);
// init block blob client
// (3) same as #1 & #2
const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(imageName);
// save file
// (4) same as #1,2, & 3
// (5) manipulate returned value
// (6) throw cause method to internally throw error
await blockBlobClient.upload(imageBuffer, imageBuffer.length, { blobHTTPHeaders: { blobContentType: 'image/png' } });
return Promise.resolve();
} catch (err: Error) {
return Promise.reject(err);
}
}
I've setup a manual mock for the module in the ./__mocks/#azure/storage-blob.ts as follows:
const MockStorageBlob = jest.createMockFromModule('#azure/storage-blob');
/**
* Utility method throw exception in the `BlockBlobClient.upload()` method.
*/
(MockStorageBlob as any).__setBlockBlobUpload_toFail = () => {
(MockStorageBlob as any).BlobServiceClient = jest.fn().mockImplementation(() => {
return {
getContainerClient: jest.fn().mockReturnValue({
getBlockBlobClient: jest.fn().mockReturnValue({
upload: jest.fn().mockImplementation(() => {
throw new Error('synthetic error');
})
})
})
}
});
}
module.exports = MockStorageBlob;
In my test, I can successfully test for #6 above like this:
import {
BlockBlobClient,
BlockBlobUploadResponse
} from '#azure/storage-blob';
import { saveImageToCDN as functionUnderTest } from './saveImageToCDN';
// mock Azure Storage blob NPM package
jest.mock('#azure/storage-blob');
describe('check expected with failure', () => {
beforeEach((done) => {
// reset number of times things have been called
jest.clearAllMocks();
done();
});
test(`it calls 'trackException()' when upload throws exception`, async (done) => {
expect.assertions(1);
// setup test
require('#azure/storage-blob').__setBlockBlobUpload_toFail();
// run SUT
const imageBuffer = Buffer.from('test string');
functionUnderTest(imageBuffer, 'imageName.png')
.then(() => {
expect(new Error('should not reach this')).toBeUndefined();
})
.catch((err: Error) => {
expect(err).toBeDefined();
})
.finally(() => {
done();
});
});
});
... but I can't figure out the correct syntax to spy on the upload() method (#4), or any of the other things I'm trying to test for (#1-5). If it matters, using Jest v26 on Node v14.
Could the __setBlockBlobUpload_toFail function return references to the mock functions ?
That would give something like this :
const MockStorageBlob = jest.createMockFromModule('#azure/storage-blob');
/**
* Utility method throw exception in the `BlockBlobClient.upload()` method.
*/
(MockStorageBlob as any).__setBlockBlobUpload_toFail = () => {
const upload = jest.fn().mockImplementation(() => {
throw new Error('synthetic error');
});
const getBlockBlobClient = jest.fn().mockReturnValue({ upload });
const getContainerClient = jest.fn().mockReturnValue({ getBlockBlobClient });
const BlobServiceClient = jest.fn().mockImplementation(() => {
return {
getContainerClient
}
});
(MockStorageBlob as any).BlobServiceClient = BlobServiceClient;
return {
upload,
getBlockBlobClient,
getContainerClient,
BlobServiceClient
};
}
module.exports = MockStorageBlob;
And in your test you would retrieve them like :
// setup test
const mockFns = require('#azure/storage-blob').__setBlockBlobUpload_toFail();
// run SUT
const imageBuffer = Buffer.from('test string');
functionUnderTest(imageBuffer, 'imageName.png')
.then(() => {
expect(new Error('should not reach this')).toBeUndefined();
})
.catch((err: Error) => {
expect(mockFns.getBlockBlobClient.mock.calls[0][0]).toBe('imageName.png')
expect(err).toBeDefined();
})
.finally(() => {
done();
});

Proper way to test type-graphql middleware with jest

Context
I am trying to write a jest test for an authentication middleware for a resolver function. I am attempting to mock an implementation so that the next function is called so that the test passes.
Error
The error I receive is "next is not a function". I can verify that the mocked function is called through expect(isAuth).toHaveBeenCalledTimes(1);, but there is clearly an issue with my mocked implementation. Any help is much appreciated.
Code
//isAuth Middleware
import { MiddlewareFn } from "type-graphql";
import { Context } from "../utils/interfaces/context";
export const isAuth: MiddlewareFn<Context> = ({ context }, next) => {
const loggedInUserId = context.req.session.id;
if (!loggedInUserId) {
throw new Error("Not authenticated!");
}
return next();
};
//transaction.test.ts
jest.mock("../middleware/isAuth", () => {
return {
isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next())
};
});
test("should create a txn successfully", async () => {
//ARRANGE
const user = await createUser(orm);
const txn = createTxnOptions();
const txnToBeCreated = { ...txn, userId: user.id };
//ACT
const response = await testClientMutate(
TXN_QUERIES_AND_MUTATIONS.CREATE_TXN,
{
variables: txnToBeCreated,
}
);
//expect(isAuth).toHaveBeenCalledTimes(1); passes so it's getting called
console.log(response);
const newlyCreatedTxn: Transaction = (response.data as any)
?.createTransaction;
//ASSERT
const dbTxn = await em.findOne(Transaction, {
id: newlyCreatedTxn.id,
});
expect(newlyCreatedTxn.id).toBe(dbTxn?.id);
});
//transaction.resolver.ts
import { Transaction } from "../entities/Transaction";
import {
Arg,
Ctx,
Mutation,
Query,
Resolver,
UseMiddleware,
} from "type-graphql";
import { Context } from "../utils/interfaces/context";
import { isAuth } from "../middleware/isAuth";
#Mutation(() => Transaction)
#UseMiddleware(isAuth)
async createTransaction(
#Arg("title") title: string,
#Arg("userId") userId: string,
#Ctx() { em }: Context
): Promise<Transaction> {
const transaction = em.create(Transaction, {
title,
user: userId,
});
await em.persistAndFlush(transaction);
return transaction;
}
Replace
jest.mock("../middleware/isAuth", () => {
return {
isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next())
};
});
With
jest.mock("../middleware/isAuth", () => {
return {
isAuth: (_, next) => next()
};
});

In Jest, how do I mock my Mongoose document's "get" method?

I'm using NodeJS and a MongoDB. I have this simple function for returning a generic property of a document ...
import mongoose, { Document, Schema } from "mongoose";
export interface IMyObject extends Document {
...
}
...
export async function getProperty(
req: CustomRequest<MyDto>,
res: Response,
next: NextFunction
): Promise<void> {
const {
params: { propertyName, code },
} = req;
try {
const my_obj = await MyObject.findOne({ code });
const propertyValue = my_obj ? my_obj.get(propertyName) : null;
if (propertyValue) {
res.status(200).json(propertyValue);
...
I'm struggling to figure out how to test this function. In particular, how do I mock an instance of my object that's compatible with the "get" method? I tried this
it("Should return the proper result", async () => {
const myObject = {
name: "jon",
};
MyObject.findOne = jest.fn().mockResolvedValue(myObject.name);
const resp = await superTestApp.get(
"/getProperty/name/7777"
);
expect(resp.status).toBe(StatusCodes.OK);
expect(resp.body).toEqual("happy");
but this fails with
TypeError: my_object.get is not a function
You would need to spy your object and its methods. Something like:
import MyObject from '..';
const mockedData = {
get: (v) => v
};
let objectSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
objectSpy = jest.spyOn(MyObject, 'findOne');
objectSpy.mockReturnValue(mockedData);
});
// clear the mock the method after all tests execution
afterAll(() => {
objectSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', () => {
const response = MyObject.findOne();
expect(response.get('whatever')).toEqual(mockedData.get('whatever'));
});

How to spy on properties returned from mocked file

Using Jest, I would like to mock one of my dependencies, but I would like to spy on the properties that mock is returning. For example, something like:
const mockGet = jest.fn(() => {});
const mockPost = jest.fn(() => {});
const mockDel = jest.fn(() => {});
jest.mock('../../services/MyService', () => {
return {
mockGet,
mockPost,
mockDel
};
});
// ...
describe('MyService', () => {
it('should do something', () => {
myService.doThis();
expect(mockGet).toHaveBeenCalled();
});
});
The problem is this gives an error jest.mock() is not allowed to reference any out-of-scope variables.
The only way I can get it to work so far is by defining the mocks inside the call to jest.mock:
jest.mock('../../services/MyService', () => {
return {
mockGet: jest.fn(() => {}),
mockPost: jest.fn(() => {}),
mockDel: jest.fn(() => {})
};
});
But then how do I access the properties to see if they were called?
Here is the solution:
Service.ts:
class Service {
public get() {
return 'real data';
}
public post() {}
public del() {}
public doThis() {
console.log('real do this');
return this.get();
}
}
export { Service };
Service.spec.ts:
import { Service } from './service';
const service = new Service();
// console.log(service);
jest.mock('./service.ts', () => {
const originalModule = jest.requireActual('./service.ts');
const { Service: OriginalService } = originalModule;
const ServiceMocked = {
...OriginalService.prototype,
get: jest.fn(),
post: jest.fn(),
del: jest.fn()
};
return {
Service: jest.fn(() => ServiceMocked)
};
});
describe('Service', () => {
it('should do something', () => {
service.get = jest.fn().mockReturnValueOnce('mocked data');
const actualValue = service.doThis();
expect(actualValue).toBe('mocked data');
expect(service.get).toHaveBeenCalled();
});
});
Unit test result:
PASS src/stackoverflow/57172774/service.spec.ts
Service
✓ should do something (11ms)
console.log src/stackoverflow/57172774/service.ts:6
real do this
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.336s, estimated 3s