I want to test a nest api, for that I create one file .env and for my dev I create a .env.development file.
Which I launch some test with jest my first test uses the good file: .env but the second test use the wrong file .env.development.
How I load the .env in nest :
import {Module} from '#nestjs/common';
import {ConfigService} from './config.service';
#Module({
providers: [{
provide: ConfigService,
useValue: new ConfigService(`.env${process.env.NODE_ENV}`),
}],
exports: [ConfigService],
})
export class ConfigModule {}
How I load the .env in jest :
"test:watch": "NODE_ENV='' jest --watch",
"jest": {
"displayName": {
"name": "API",
"color": "yellow"
},
"verbose": true,
"runner": "jest-serial-runner",
"testSequencer": "../src/test/sequencer.jest.js",
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node",
"preset": "#shelf/jest-mongodb",
"setupFiles": [
"dotenv/config" <------------------------| here
]
},
The test for my .env (is ok the right .env is loaded)
describe('🛠️ tests for env file', () => {
it('should have a db_user', () => {
expect(process.env.db_user).toBeDefined();
});
it('should have a db_pass', () => {
expect(process.env.db_pass).toBeDefined();
});
it('should have a db_uri', () => {
expect(process.env.db_uri).toBeDefined();
});
it('should have a db_port', () => {
expect(process.env.db_port).toBeDefined();
});
it('should have a db_name', () => {
expect(process.env.db_name).toBeDefined();
});
});
The test that loads the wrong .env (I put a wrong ip for the db in .env.development, so the test failed)
import { Test, TestingModule } from '#nestjs/testing';
import * as request from 'supertest';
import {AppModule} from '../app.module';
// remove console.log
console.log = jest.fn();
describe('🗜️ UserController', () => {
let app;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
/** valid test for create a admin user */
it('post /users valid test for create a admin user', () => {
return request(app.getHttpServer())
.post('/users')
.send({
jwt: '',
firstname: 'admin',
lastname: 'admin',
mail: 'admin#gmail.com',
password: 'test'
})
.expect(201);
});
});
It looks like there is a missing . after .env in this line:
new ConfigService(`.env${process.env.NODE_ENV}`)
However, the extra period is only needed when process.env.NODE_ENV contains a value so I would do something like:
const NODE_ENV = process.env.NODE_ENV ? `.${process.env.NODE_ENV}` : '';
//...
new ConfigService(`.env${NODE_ENV}`)
This way you will either end up with new ConfigService('.env') or new ConfigService('.env.development')
Related
I am trying to write an e2e test for my NestJS microservice that has a gRPC client. No matter what I do to try and mock the ClientsModule in the e2e test it still seems to not pick up the config and is unable to locate the *.proto file.
Please find a sample of my code below:
The clients module in the app.module.ts
// src/app.module.ts
#Module({
imports: [
HttpModule,
...
...
ClientsModule.register([
{
name: 'USERS_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'users',
credentials: credentials.createInsecure(),
protoPath: join(__dirname, '../proto/users.proto'),
url: 'users-grpc-server.users:50051',
},
},
]),
],
controllers: [UsersController],
providers: [UsersService, UsersClient],
})
export class AppModule {}
The users client to make the gRPC call to the gRPC Server NestJS microservice:
// src/clients/users.client.ts
#Injectable()
export class UsersClient implements OnModuleInit {
private readonly logger: Logger = new Logger(UsersClient.name);
private readonly API_KEY: string = this.configService.get<string>('services.v1.api-key');
private usersController: usersController;
constructor(#Inject('USERS_PACKAGE') private client: ClientGrpc, private configService: ConfigService) {}
onModuleInit() {
this.usersController = this.client.getService<UsersController>('UsersController');
}
async getUsers(headers: IncomingHttpHeaders, userId: string): Promise<Users> {
let users: Users = null;
const metadata = new Metadata();
metadata.add('Authorization', headers.authorization);
metadata.add('x-api-key', this.API_KEY);
try {
users = await this.usersController.getUsers({}, metadata);
} catch (e) {
const { message, response: { status = 500 } = {}, stack } = e;
this.logger.error(stack);
throw new HttpException(message, status);
}
return users;
}
}
The update nest-cli.json
{
"collection": "#nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}, {"include": "**/*.proto", "outDir": "./dist"}]
}
}
The e2e test configuration
// test/app.e2e-spec.json
let app: NestFastifyApplication;
let httpService: HttpService;
let cacheManager: Cache;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
HttpModule,
...
...
ClientsModule,
],
})
.overrideProvider(ClientsModule)
.useValue({
name: 'USERS_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'users',
credentials: ServerCredentials.createInsecure(),
protoPath: join(__dirname, '../src/proto/users.proto'),
url: 'localhost:50051',
},
})
.compile();
app = module.createNestApplication(new FastifyAdapter());
await app.init();
await app.getHttpAdapter().getInstance().ready();
httpService = module.get<HttpService>(HttpService);
cacheManager = module.get<Cache>(CACHE_MANAGER);
});
The error:
● Test suite failed to run
The invalid .proto definition (file at "/users-service-grpc-client/app/proto/users.proto" not found)
> 26 | ClientsModule.register([
| ^
27 | {
28 | name: 'USERS_PACKAGE',
29 | transport: Transport.GRPC,
at ClientGrpcProxy.loadProto (node_modules/#nestjs/microservices/client/client-grpc.js:220:39)
at ClientGrpcProxy.createClients (node_modules/#nestjs/microservices/client/client-grpc.js:193:34)
at new ClientGrpcProxy (node_modules/#nestjs/microservices/client/client-grpc.js:29:33)
at Function.create (node_modules/#nestjs/microservices/client/client-proxy-factory.js:27:24)
at node_modules/#nestjs/microservices/module/clients.module.js:12:80
at Array.map (<anonymous>)
at Function.register (node_modules/#nestjs/microservices/module/clients.module.js:10:41)
at Object.<anonymous> (src/app.module.ts:26:19)
It seems like the the e2e test isnt using the ClientsModule configuration from the test and is still using the ClientsModule configuration from the app.module.ts.
Does anyone know of a way to configure this correctly ?
I am not sure how this is working but I changed the ClientsModule in the src/app.module.ts to be registerAsync because I wanted to make the url dynamic and it now seems to be working correctly
// src/app.module.ts
#Module({
imports: [
HttpModule,
...
...
ClientsModule.registerAsync([
{
name: 'USERS_PACKAGE',
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'users',
credentials: credentials.createInsecure(),
protoPath: join(__dirname, '../proto/users.proto'),
url: configService.get<string>('services.users-grpc-server-service.url'),
},
}),
inject: [ConfigService],
},
]),
],
controllers: [UsersController],
providers: [UsersService, UsersClient],
})
export class AppModule {}
Note: In the E2E test, I also had to mock the grpc call using overrideProvider
// test/app.e2e-spec.ts
const module: TestingModule = await Test.createTestingModule({
imports: [
HttpModule,
...
...
ClientsModule,
],
})
.overrideProvider('USERS_PACKAGE')
.useValue({
getService: () => ({
getUsers: jest.fn().mockReturnValue({
"name": "test user",
"age": "55",
"height": "1.89"
}),
}),
})
.compile();
My test runs indefinitely without results (see image below). I am pretty new to setting up backend testing environment. Any guidance is much appreciated.
I have provided the snippets of my code, please let me know if you need any additional information.
I tried the below commands.
--clearCache option which did not work
--runInBand option lead to a new error, error Command failed with signal "SIGSEGV".
// testHelper.js
import { DataSource, DataSourceOptions } from "typeorm";
import Database from "better-sqlite3";
export class TestHelper {
private static _instance: TestHelper;
private constructor() { }
public static get instance(): TestHelper {
if (!this._instance) this._instance = new TestHelper();
return this._instance;
}
private dbConnect!: DataSource;
private testdb!: any;
async setupTestDB() {
this.testdb = new Database(":memory:", { verbose: console.log });
this.dbConnect = new DataSource({
name: "default",
type: "better-sqlite3",
database: ":memory:",
entities: ["src/entities/**/*.ts"],
synchronize: true,
} as DataSourceOptions);
await this.dbConnect.initialize()
}
teardownTestDB() {
this.dbConnect.destroy();
this.testdb.close();
}
}
// test.ts
import MyEntity from "entities/MyEntity";
import { TestHelper } from './__utils/testHelper';
beforeAll(async () => {
return await TestHelper.instance.setupTestDB();
});
afterAll(() => {
return TestHelper.instance.teardownTestDB();
});
test("create test data and fetch it", async () => {
await MyEntity.insert({
name: "sqlTestEntity"
});
let sqlTestEntity = await MyEntity.find({
where: {
id: '1'
}
});
expect(sqlTestEntity[0].name).toBe("sqlTestEntity");
});
//package.json
"scripts": {
....
"test": "NODE_OPTIONS=--experimental-vm-modules npx jest",
....
}
"typeorm": "0.3",
"#types/jest": "^28.1.7",
"jest": "^28.1.3",
// jest.config.js
/** #type {import('ts-jest/dist/types').InitialOptionsTsJest} */
export default {
preset: 'ts-jest',
testEnvironment: 'node',
testTimeout: 20000,
roots: ['src'],
modulePaths: ['<rootDir>/src'],
testPathIgnorePatterns: [
'<rootDir>/node_modules/', "/__utils"
],
moduleDirectories: [
"src"
],
transform: {
"^.+\\.js?$": "babel-jest",
"^.+\\.ts?$": "ts-jest"
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(js?|tsx?)$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
testEnvironment: "node",
"moduleNameMapper": {
"src/(.*)": "<rootDir>/src/$1"
},
globals: {
"ts-jest": {
"useESM": true
}
},
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
};
I have a Typescript Mocha setup that I'm using to test my Firestore rules. It uses the latest version of the #firebase/rules-unit-testing library that is part of the Version 9 Firebase SDK.
It also uses the latest Firestore client library that has the newer API that is tree-shakable.
My problem is that Mocha doesn't seem to be able to recognise tests in my nested describe() structure AFTER I call the initializeTestEnvironment() promise.
If you see the output you will see only two tests get executed.
package.json
{
"name": "blocks",
"version": "1.0.0",
"description": "A new Flutter project.",
"main": "index.js",
"private": true,
"scripts": {
"setup": "firebase setup:emulators:firestore",
"test": "firebase emulators:exec --only firestore \"mocha firestore.rules.spec.ts\""
},
"devDependencies": {
"#firebase/rules-unit-testing": "^2.0.2",
"#types/mocha": "^8.2.2",
"firebase-tools": "^10.7.1",
"chai": "^4.3.4",
"mocha": "^9.2.2",
"ts-node": "^9.1.1",
"typescript": "^4.2.4"
},
"mocha": {
"recursive": true,
"reporter": "nyan",
"require": "ts-node/register",
"watch-extensions": "ts"
},
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
}
tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true,
"importHelpers": true,
"module": "commonjs",
"target": "es2019",
"types": [
"node"
]
}
}
firestore.rules.spec.ts
import { before, beforeEach, after } from 'mocha'
import { readFileSync } from 'fs'
import {
assertFails,
assertSucceeds,
initializeTestEnvironment,
} from '#firebase/rules-unit-testing'
import { doc, setDoc, getDoc, updateDoc, deleteDoc } from 'firebase/firestore'
const PROJECT_ID = 'blocks-firestore-rules-testing'
describe('A test that should get run', async () => {
it('should pass', async () => {
return true
})
})
describe('Firestore rules', async () => {
it('should pass also', async () => {
return true
})
const testEnv = await initializeTestEnvironment({
projectId: PROJECT_ID,
firestore: {
rules: readFileSync('firestore.rules', 'utf-8'),
},
})
beforeEach(async () => {
testEnv.cleanup()
await testEnv.clearFirestore()
})
after(async () => {
testEnv.cleanup()
await testEnv.clearFirestore()
})
const uid = 'normal-user'
const user = testEnv.authenticatedContext(uid)
const userRef = doc(user.firestore(), `users/${uid}/`)
const dummyUserDoc = { displayName: 'John Smith' }
it('ensures can create their own user document', async () => {
await assertSucceeds(setDoc(userRef, dummyUserDoc))
})
it('ensures they can read their user document', async () => {
testEnv.withSecurityRulesDisabled(async () => {
setDoc(userRef, dummyUserDoc)
})
await assertSucceeds(getDoc(userRef))
})
it('ensures they can update their user document', async () => {
testEnv.withSecurityRulesDisabled(async () => {
setDoc(userRef, dummyUserDoc)
})
await assertSucceeds(updateDoc(userRef, { foo: 'baz' }))
})
it('ensures cannot delete their user document', async () => {
testEnv.withSecurityRulesDisabled(async () => {
setDoc(userRef, dummyUserDoc)
})
await assertFails(deleteDoc(userRef))
})
})
Command line output:
➜ blocks git:(main) ✗ npm test
> blocks#1.0.0 test
> firebase emulators:exec --only firestore "mocha firestore.rules.spec.ts"
i emulators: Starting emulators: firestore
i firestore: Firestore Emulator logging to firestore-debug.log
i Running script: mocha firestore.rules.spec.ts
2 -_-_,------,
0 -_-_| /\_/\
0 -_-^|__( ^ .^)
-_- "" ""
2 passing (2ms)
✔ Script exited successfully (code 0)
i emulators: Shutting down emulators.
i firestore: Stopping Firestore Emulator
i hub: Stopping emulator hub
I need to do some unit test to a kafka implementation in my project with NestJS but I don't know how to do it.
I have a Service thats inject a Client Kafka
export class Service {
private static readonly logger = new Logger(ProducerService.name);
constructor(
#Inject('kafka-registrar') private client: ClientKafka,
private someOtherService: SomeOtherService,
) {}
Module
#Module({
imports: [
ClientsModule.register([
{
name: 'kafka-registrar',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'hero',
brokers: ['localhost:9092'],
},
consumer: {
groupId: '1',
},
},
},
]),
SomeOtherService,
],
providers: [Service],
})
export class Module {}
Unit test
describe('Test Controller', () => {
let clientKafka: ClientKafka;
let someOtherService: SomeOtherService;
let producerService: ProducerService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
ProducerService,
{
provide: SchemaRegistryService,
useValue: {
encodeWithId: jest.fn(),
},
},
{
provide: ClientKafka,
useValue: {
emit: jest.fn(),
},
},
],
}).compile()
clientKafka = moduleRef.get(ClientKafka);
schemaRegistryService = moduleRef.get(SchemaRegistryService);
producerService = moduleRef.get(ProducerService);
});
The project give me this error:
Error: Nest can't resolve dependencies of the ProducerService (?, SchemaRegistryService). Please make sure that the argument kafka-registrar at index [0] is available in the RootTestModule context.
Potential solutions:
- If kafka-registrar is a provider, is it part of the current RootTestModule?
- If kafka-registrar is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing kafka-registrar */ ]
})
I don't know how to resolve this in NestJS. For example in Java,I belive that this can be with #Mock ClientKafka clientKafka bit I dont have any other experience with NestJS... Please helpme! :)
In your test file, you can change provide: ClientKafka to this provide: 'kafka-registrar'.
const moduleRef = await Test.createTestingModule({
providers: [
ProducerService,
{
provide: SchemaRegistryService,
useValue: {
encodeWithId: jest.fn(),
},
},
{
provide: 'kafka-registrar',
useValue: {
emit: jest.fn(),
},
},
],
}).compile()
global variable in JS is not able to access in jest test files, also not able to change the values which are already defined in global.
the work around I have did is as follow:
require('./../../../../bin/global');
describe('gloablAccessMethod', () => {
beforeEach(() => {
// jest.spyOn(regionConfig,'global.regionConfig').mockImplementation(() => ({
// 'ENABLE_CONFIG' : false
// }));
global.regionConfig.ENABLE_CONFIG = false;
});
const PHONE_PREFIX_SG = 65;
it('should access mock global variables', () =>{
//some sample tests
});
});
jest configuration
"jest": {
"timers": "fake",
"testEnvironment": "node",
"setupFiles": [
"./.jest/setEnv.js"
],
"setupFilesAfterEnv": [
"./.jest/setup.js"
],
"coverageReporters": [
"text",
"cobertura"
],
"collectCoverage": true,
"collectCoverageFrom": [
"src/**/*.{js,jsx}"
]
},```