How to set/mock an env variable in vitest (`process.env.NODE_ENV = 'anything'` takes effect only in test file)? - unit-testing

I have a method, in a class, that only executes its action when NODE_ENV === 'test'.
Here is the test that I set the env to anything to test the failing scenario:
it('returns Left on clearDatabase when not in test environment', async () => {
const { sut } = await makeSut()
process.env.NODE_ENV = 'any_environment'
const result = await sut.clearDatabase()
process.env.NODE_ENV = 'test'
expect(result.isLeft()).toBe(true)
})
Here is the method:
async clearDatabase (): Promise<Either<Error, void>> {
if (process.env.NODE_ENV !== 'test') {
return left(new Error('Clear database is allowed only in test environment'))
}
try {
const { database } = this.props.dataSource
await this.mongoClient.db(database).dropDatabase()
return right()
} catch (error) {
return left(error)
}
}
The problem is that when the method do it's verification, the value in NODE_ENV wasn't changed at all, it has its initial value (test). If I log the value, after setting it, in test file it's there, only the object can't see this change. In jest it works just fine. How can I set/mock it properly in vitest?
Here you find a StackBlitz with an example scenario: https://stackblitz.com/edit/node-lr72gz?file=test/example.unit.test.ts&view=editor

Related

Unit Testing Passport Strategy / NestJS Guard

I have a JWTGuard() decorator which follows the implementation as described in https://docs.nestjs.com/security/authentication#jwt-functionality
It looks like so:
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard } from '#nestjs/passport';
import { IS_PUBLIC_ROUTE } from '../decorators/public-route.decorator';
#Injectable()
export class JWTAuthenticationGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(
IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()],
);
if (isPublic) return true;
return super.canActivate(context)
}
}
It works as expected, but having difficulty unit testing the guard. In short, I want to test three things:
If IS_PUBLIC_ROUTE is set, then canActivate() returns true ✅ this unit test works
If an expired/invalid/incorrectly signed token is passed, canActivate() returns false ❌
If a valid token is found, canActivate() returns true ❌
I tried to follow https://github.com/jmcdo29/testing-nestjs/blob/master/apps/complex-sample/src/cat/cat.guard.spec.ts which just tests a bare guard, not extending a Passport Guard.
Tests are setup as follows:
let guard: JWTAuthenticationGuard;
const reflector = new Reflector();
beforeEach(() => {
guard = new JWTAuthenticationGuard(reflector);
passport.use('jwt', new JWTStrategy());
});
The first test (should return false)
it('should return false if no authorization token is found', () => {
const context = createMock<ExecutionContext>();
const result = guard.canActivate(context);
expect(result).toBe(false);
});
My test never even makes it to the expect() clause, as it has thrown an UnauthorisedException and the test suite exits entirely.
I tried debugging inside the canActivate() method, and checking the type of super.canActivate() which tells me that it's a pending promise. I tried awaiting it, but still says its Pending, I tried wrapping in a try/catch, but the catch block never runs. Reading through the code of passport's AuthGuard, it never throws either.
I tried to add a valid token:
it('should return false if no authorization token is found', async () => {
const context = createMock<ExecutionContext>();
expect(context.switchToHttp()).toBeDefined();
context.switchToHttp().getRequest.mockReturnValue({
headers: {
Authorization:
'Bearer ey...[truncated]...dE',
},
});
const result = await guard.canActivate(context);
expect(result).toBe(true);
however I still just get an UnauthorisedException thrown from god knows where at the result = guard.can... line, and still never makes it to the expect.
Any guidance would be appriciated.

Trouble Writing to Jest Mocked Prisma Database

I have two databases that I need to interact with in my code. I have a simple function that takes an object and writes it to my PostgreSQL database using Prisma. I've tested the function with Postman, and it works perfectly, but when I try to execute it using a Jest mock (using the singleton pattern found in the Prisma unit testing guide), it returns undefined indicating that it didn't interact with the database and create the new record. Here's my code:
/prisma/clinical-schema.prisma
generator client {
provider = "prisma-client-js"
output = "./generated/clinical"
}
datasource clinicalDatabase {
provider = "postgresql"
url = "postgresql://postgres:postgres#localhost:5432/clinical-data?schema=public"
}
model pcc_webhook_update {
id Int #id #default(autoincrement())
event_type String
organization_id Int
facility_id Int
patient_id Int
resource_id String?
webhook_date DateTime #default(now()) #clinicalDatabase.Timestamptz(6)
status pcc_webhook_update_status #default(pending)
status_changed_date DateTime? #clinicalDatabase.Timestamptz(6)
error_count Int #default(0)
##unique([organization_id, facility_id, patient_id, resource_id, event_type, status])
}
enum pcc_webhook_update_status {
pending
processing
processed
error
}
/prisma/clinical-client.ts
import { PrismaClient } from './generated/clinical';
const prismaClinical = new PrismaClient();
export default prismaClinical;
/testing/prisma-clinical-mock.ts
import { PrismaClient } from '../prisma/generated/clinical';
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import prisma from '../prisma/clinical-client';
jest.mock('../prisma/clinical-client', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}));
beforeEach(() => {
mockReset(prismaClinicalMock);
});
export const prismaClinicalMock = prisma as unknown as DeepMockProxy<PrismaClient>;
Everything up to this point follows the conventions outlined by the Prisma unit testing docs. The only modification I made was to make it database specific. Below is my function and tests. The request object in handle-pcc-webhooks.ts is a sample http request object, the body of which contains the webhook data I care about.
/functions/handle-pcc-webhooks/handler.ts
import prismaClinical from '../../../prisma/clinical-client';
import { pcc_webhook_update } from '../../../prisma/generated/clinical';
import { requestObject } from './handler.types';
export const handlePccWebhook = async (request: requestObject) => {
try {
const webhook = JSON.parse(request.body);
// if the webhook doesn't include a resource id array, set it to an array with an empty string to ensure processing and avoid violating
// the multi-column unique constraint on the table
const { resourceId: resourceIds = [''] } = webhook;
let records = [];
for (const resourceId of resourceIds) {
// update an existing record if one exists in the pending state, otherwise create a new entry
const record: pcc_webhook_update = await prismaClinical.pcc_webhook_update.upsert({
where: {
organization_id_facility_id_patient_id_resource_id_event_type_status: {
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
event_type: webhook.eventType,
status: 'pending'
}
},
update: {
webhook_date: new Date()
},
create: {
event_type: webhook.eventType,
organization_id: webhook.orgId,
facility_id: webhook.facId,
patient_id: webhook.patientId,
resource_id: resourceId,
status: 'pending' // not needed
}
});
records.push(record);
}
return records;
} catch (error) {
console.error(error);
}
};
/functions/handle-pcc-webhooks/handler.spec.ts
import fs from 'fs';
import path from 'path';
import MockDate from 'mockdate';
import { prismaClinicalMock } from '../../../testing/prisma-clinical-mock';
import { createAllergyAddRecord } from './__mocks__/allergy';
import { requestObject } from './handler.types';
import { handlePccWebhook } from './handler';
describe('allergy.add', () => {
let requestObject: requestObject;
let allergyAddRecord: any;
beforeAll(() => {
requestObject = getRequestObject('allergy.add');
});
beforeEach(() => {
MockDate.set(new Date('1/1/2022'));
allergyAddRecord = createAllergyAddRecord(new Date());
});
afterEach(() => {
MockDate.reset();
});
test('should create an allergy.add database entry', async() => {
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
// this is where I would expect handlePccWebhook to return the newly created database
// record, but instead it returns undefined. If I run the function outside of this
// unit test, with the same input value, it functions perfectly
await expect(handlePccWebhook(requestObject)).resolves.toEqual([allergyAddRecord]);
});
});
// This just builds a request object with the current webhook being tested
function getRequestObject(webhookType: string) {
// read the contents of request object file as a buffer, then convert it to JSON
const rawRequestObject = fs.readFileSync(path.resolve(__dirname, '../../sample-data/handle-pcc-webhook-request.json'));
const requestObject: requestObject = JSON.parse(rawRequestObject.toString());
// read the contents of the webhook file as a buffer, then convert it to a string
const rawWebhook = fs.readFileSync(path.resolve(__dirname, `../../sample-data/${webhookType}.json`));
const webhookString = rawWebhook.toString();
// set the body of the request object to the contents of the target webhook
requestObject.body = webhookString;
return requestObject;
}
Finally, here is the result of running the unit test:
So after banging my had against the wall for a few hours, I figured out the issue. In my handler.spec.ts file, I had the following line:
prismaClinicalMock.pcc_webhook_update.create.mockResolvedValue(allergyAddRecord);
what that does is mock the value returned for any create functions run using Prisma. The issue is that my function is using an upsert function, which I wasn't explicitly mocking, thus returning undefined. I changed the above line to
prismaClinicalMock.pcc_webhook_update.upsert.mockResolvedValue(allergyAddRecord);
and it started working.

jest.spyOn mock return value not returning value

The code I'm trying to test:
const utils = require('../utils/utils');
let imageBuffer;
try {
imageBuffer = await utils.retrieveImageFromURI(params)
console.log(imageBuffer) // comes back as undefined when I mock the utils.retreieveImageFromURI
if (!imageBuffer || imageBuffer.length < 1024) {
throw new Error(`Retrieve from uri (${params.camera.ingest.uri}) was less than 1kb in size - indicating an error`)
}
console.log(`${params.camera.camId} - Successful Ingestion from URI`);
} catch (err) {
reject({ 'Task': `Attempting to pull image from camera (${params.camera.camId}) at ${params.camera.ingest.uri}`, 'Error': err.message, 'Stack': err.stack })
return;
}
Specifically, I'm trying to mock the utils.retrieveImageFromURI function - which has API calls and other things in it.
When I try to mock the function using spyOn I am trying it like so:
describe("FUNCTION: ingestAndSave", () => {
let fakeImageBuffer = Array(1200).fill('a').join('b'); // just get a long string
console.log(fakeImageBuffer.length) //2399
let retrieveImageFromURISpy
beforeAll(() => {
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(fakeImageBuffer)
})
test("Will call retrieveImageFromURI", async () => {
await ingest.ingestAndSave({camera:TEST_CONSTANTS.validCameraObject, sourceQueueURL:"httpexamplecom", receiptHandle: "1234abcd"})
expect(retrieveImageFromURISpy).toHaveBeenCalledTimes(1)
})
afterEach(() => {
jest.resetAllMocks()
})
afterAll(() => {
jest.restoreAllMocks()
})
})
When I do this, I get a console log that imageBuffer (which is supposed to be the return of the mocked function) is undefined and that, in turn, triggers the thrown Error that "Retrieve from uri ...." ... which causes my test to fail. I know I could wrap the test call in a try/catch but the very next test will be a "does not throw error" test... so this needs to be solved.
It's not clear to me why the mockReturnValue isn't getting returned.
Other steps:
I've gone to the REAL retrieveImageFromURI function and added a console log - it is not running.
I've changed mockReturnValue to mockImplementation like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return fakeImageBuffer
})
And it does NOT console log 'here'. I'm unsure why not.
I have also tried to return it as a resolved Promise, like so:
retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockImplementation(() => {
console.log("Here")
return Promise.resolve(fakeImageBuffer)
})
Note, this also doesn't console log.
I've also tried to return the promise directly with a mockReturnValue:
`retrieveImageFromURISpy = jest.spyOn(utils, 'retrieveImageFromURI').mockReturnValue(Promise.resolve(fakeImageBuffer)`)

How to make stub for mongoose document object

I am writing unit test for my auth.service module validateReader unit,
async validateReader(username: string, password: string): Promise<any> {
const reader = await this.readerService.findOne(username);
const match = await bcrypt.compare(password, reader.password);
if (match) {
const { password, ...result } = reader.toJSON();
this.logger.info(
`Reader ${reader.username} username & password validation passed`,
);
return result;
}
this.logger.warn(`Incorrect password in reader ${reader.username} login`);
return null;
}
I tried to mock readerService.findOne function as following:
jest
.spyOn(readerService, 'findOne')
.mockImplementationOnce(() => Promise.resolve(readerStub()));
but did not work, always got error - Cannot spy the findOne property because it is not a function; I think the reason is the returned value must be mongoose document object (need toJSON() method), but my readerStub() just return a reader object, missing lots of document properties. Is there anyway I can set up stub for document & reader? And maybe my analysis is wrong, there is other reason to got this error.
Following is my mock readerService:
export const ReaderService = jest.fn().mockReturnValue({
register: jest.fn().mockResolvedValue(readerStub()),
findOne: jest.fn().mockResolvedValue(readerStub()),
getProfile: jest.fn().mockResolvedValue(readerStub()),
updateProfile: jest.fn().mockResolvedValue(readerStub()._id),
changePwd: jest.fn().mockResolvedValue(readerStub().username),
login: jest.fn().mockResolvedValue(accessTokenStub()),
tokenRefresh: jest.fn().mockReturnValue(accessTokenStub()),
logout: jest.fn().mockResolvedValue(readerStub()._id),
});

How to use CodeceptJS to unit-test a JS function

I've set up CodeceptJS for a project and use it to test various end-to-end scenarios.
Now I want to extend the tests-suite to also run unit-tests to verify functionality of custom JS functions.
For example: I have a global object App that has a version attribute. As a first test, I want to confirm that App.version is present and has a value.
My first attempt is a test.js file with the following code:
Feature('Unit Tests');
Scenario('Test App presence', ({ I }) => {
I.amOnPage('/');
I.executeScript(function() {return App.version})
.then(function(value) { I.say(value) } );
});
Problems with this code
The major issue: How can I assert that the App.version is present?
My script can display the value but does not fail if it's missing
My code is very complex for such a simple test.
I'm sure there's a cleaner/faster way to perform that test, right?
Here is a solution that works for me:
Read data from the browser:
I created a custom helper via npx codecept gh and named it BrowserAccess.
The helper function getBrowserData uses this.helpers['Puppeteer'].page.evaluate() to run and return custom code from the browser scope. Documentation for .evaluate()
Custom assertions:
Install the codeceptjs-assert package, e.g. npm i codeceptjs-assert
Add the AssertWrapper-helper to the codecept-config file. This enables checks like I.assert(a, b)
Full Code
codecept.conf.js
exports.config = {
helpers: {
AssertWrapper: {
require: "codeceptjs-assert"
},
BrowserAccess: {
require: './browseraccess_helper.js'
},
...
},
...
}
browseraccess_helper.js
const Helper = require('#codeceptjs/helper');
class BrowserAccess extends Helper {
async getBrowserData(symbolName) {
const currentPage = this.helpers['Puppeteer'].page;
let res;
try {
res = await currentPage.evaluate((evalVar) => {
let res;
try {
res = eval(evalVar);
} catch (e) {
}
return Promise.resolve(res);
}, symbolName);
} catch (err) {
res = null;
}
return res;
}
}
jsapp_test.js (the test is now async)
Feature('Unit Tests');
Scenario('Test App presence', async ({ I }) => {
I.amOnPage('/');
const version = await I.getBrowserData('App.version');
I.assertOk(version);
});