Here's an example of how I make the test get to the error line.
I've tried several ways that I found but none of them worked.
Service:
#Injectable()
export class AppConfigurationService {
private readonly _connectionString!: string;
get connectionString() {
return this._connectionString;
}
constructor(private readonly _configService: ConfigService) {
this._connectionString = this._getConnectionStringFromEnvFile();
}
private _getConnectionStringFromEnvFile(): string {
const connectionString = this._configService.get<string>('MONGODB_DB_URI');
if (!connectionString) {
throw new Error(
'No connection string has been provided in the .env file.',
);
}
return connectionString;
}
}
Service Spec:
describe('#Fail connect', () => {
let service2: AppConfigurationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot({ load: [configuration] })],
providers: [
AppConfigurationService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
if (key === 'MONGODB_DB_URI') return '';
}),
},
},
],
}).compile();
service2 = module.get<AppConfigurationService>(AppConfigurationService);
});
it('should return erro in connection', async () => {
expect(service2.connectionString).rejects.toThrow(
new Error('No connection string has been provided in the .env file.'),
);
});
});
Error:
Image Erro
Here is the error message that appears:
● #Fail connect › should return erro in connection
No connection string has been provided in the .env file.
18 |
19 | if (!connectionString) {
> 20 | throw new Error(
| ^
21 | 'No connection string has been provided in the .env file.',
22 | );
23 | }
more detail than that is impossible
The test can't ever fire because the Test.createTestingModule doesn't ever succeed. You throw an error during the construction of the service. You could do expect(() => new AppConfigurationService({ get: () => undefined })).toThrow(new Error('No connection string has been provided in the .env file.')) so that you try to create the AppConfigurationService and catch the error that is inevitably thrown by it.
I solved it as follow:
it('should return erro in connection', async () => {
try {
new AppConfigurationService({ get: () => '' } as any);
} catch (error) {
expect(error.message).toBe(
'No connection string has been provided in the .env file.',
);
}
});
Related
Imagine I have a Controller defined like so:
class NewsEndpointQueryParameters {
#IsNotEmpty()
q: string;
#IsNotEmpty()
pageNumber: number;
}
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get(['', 'ping'])
ping(): PingEndpointResponse {
return this.appService.ping();
}
#Get(['news'])
getNews(
#Query() queryParameters: NewsEndpointQueryParameters
): Observable<NewsEndpointResponse> {
return this.appService.getNews(
queryParameters.q,
queryParameters.pageNumber
);
}
}
I want to be able to test what happens in a request, if, for example, a query parameter is not provided.
Right now this is my testing setup:
describe('AppController', () => {
let app: TestingModule;
let nestApp: INestApplication;
let appService: AppService;
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
imports: [HttpModule],
}).compile();
appService = app.get<AppService>(AppService);
nestApp = app.createNestApplication();
await nestApp.init();
return;
});
describe('/', () => {
test('Return "Pong!"', async () => {
const appServiceSpy = jest.spyOn(appService, 'ping');
appServiceSpy.mockReturnValue({ message: 'Pong!' });
const response = await supertest(nestApp.getHttpServer()).get('/');
expect(response.body).toStrictEqual({
message: 'Pong!',
});
return;
});
});
describe('/ping', () => {
test('Return "Pong!"', async () => {
const appServiceSpy = jest.spyOn(appService, 'ping');
appServiceSpy.mockReturnValue({ message: 'Pong!' });
const response = await supertest(nestApp.getHttpServer()).get('/ping');
expect(response.body).toStrictEqual({
message: 'Pong!',
});
return;
});
});
describe('/news', () => {
describe('Correct query', () => {
beforeEach(() => {
const appServiceSpy = jest.spyOn(appService, 'getNews');
appServiceSpy.mockReturnValue(
new Observable<NewsEndpointResponse>((subscriber) => {
subscriber.next({
data: [{ url: 'test' }],
message: 'test',
status: 200,
});
subscriber.complete();
})
);
return;
});
test('Returns with a custom body response.', async () => {
const response = await supertest(nestApp.getHttpServer()).get(
'/news?q=test&pageNumber=1'
);
expect(response.body).toStrictEqual({
data: [{ url: 'test' }],
message: 'test',
status: 200,
});
return;
});
return;
});
describe('Incorrect query', () => {
test("Returns an error if 'q' query parameter is missing.", async () => {
return;
});
test("Returns an error if 'pageNumber' query parameter is missing.", async () => {
return;
});
return;
});
return;
});
return;
});
If I do nx serve and then curl 'localhost:3333/api/ping', I get:
{"message":"Pong!"}
And if I do curl 'localhost:3333/api/news?q=test&pageNumber=1' I get:
{"data":['lots of interesting news'],"message":"News fetched successfully!","status":200}
Finally, if I do curl 'localhost:3333/api/news?q=test' I get:
{"statusCode":400,"message":["pageNumber should not be empty"],"error":"Bad Request"}
How can I replicate the last case? If I use supertest, there is no error returned like the above. I haven't found a way to mock the Controller's function too.
A very special thank to #jmcdo29 for explaining me how to do this.
Code:
beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [
AppService,
{ provide: APP_PIPE, useValue: new ValidationPipe() },
],
imports: [HttpModule, AppModule],
}).compile();
appService = app.get<AppService>(AppService);
nestApp = app.createNestApplication();
await nestApp.init();
return;
});
Explanation:
We need to model the behavior of bootstrap() in main.ts. In my case, in looks like this:
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: environment.nestCors,
});
app.useGlobalPipes(new ValidationPipe());
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || 3333;
await app.listen(port, () => {
Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix);
});
}
Instead of importing the AppModule, we could also configure the app created for testing like so: nestApp.useGlobalPipes(new ValidationPipe()) (this needs to be done before await nestApp.init())
I'm trying to mock a fetch call using thisfetch-mock-jest but it the code still trys to go to the remote address and eventually fail with error message FetchError: request to https://some.domain.io/app-config.yaml failed, reason: getaddrinfo ENOTFOUND some.domain.io].
Here the the test code
import { AppConfig } from '#backstage/config';
import { loadConfig } from './loader';
import mockFs from 'mock-fs';
import fetchMock from 'fetch-mock-jest';
describe('loadConfig', () => {
beforeEach(() => {
fetchMock.mock({
matcher: '*',
response: `app:
title: Example App
sessionKey: 'abc123'
`
});
});
afterEach(() => {
fetchMock.mockReset();
});
it('load config from remote path', async () => {
const configUrl = 'https://some.domain.io/app-config.yaml';
await expect(
loadConfig({
configRoot: '/root',
configTargets: [{ url: configUrl }],
env: 'production',
remote: {
reloadIntervalSeconds: 30,
},
})
).resolves.toEqual([
{
context: configUrl,
data: {
app: {
title: 'Example App',
sessionKey: 'abc123',
},
},
},
]);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
function defer<T>() {
let resolve: (value: T) => void;
const promise = new Promise<T>(_resolve => {
resolve = _resolve;
});
return { promise, resolve: resolve! };
}
});
loadConfig has the fetch code that I'm trying to mock.
export async function loadConfig(
options: LoadConfigOptions,
): Promise<AppConfig[]> {
const loadRemoteConfigFiles = async () => {
const configs: AppConfig[] = [];
const readConfigFromUrl = async (remoteConfigProp: RemoteConfigProp) => {
const response = await fetch(remoteConfigProp.url);
if (!response.ok) {
throw new Error(
`Could not read config file at ${remoteConfigProp.url}`,
);
}
remoteConfigProp.oldETag = remoteConfigProp.newETag ?? undefined;
remoteConfigProp.newETag =
response.headers.get(HTTP_RESPONSE_HEADER_ETAG) ?? undefined;
remoteConfigProp.content = await response.text();
return remoteConfigProp;
};
.......
return configs;
}
let remoteConfigs: AppConfig[] = [];
if (remote) {
try {
remoteConfigs = await loadRemoteConfigFiles();
} catch (error) {
throw new Error(`Failed to read remote configuration file, ${error}`);
}
}
........ do some stuff with config then return
return remoteConfigs;
}
The config is a yaml file, that eventually gets parsed and converted into config object.
Any idea why is it failing to mock the fetch call?
replaced
import fetchMock from 'fetch-mock-jest';
with
const fetchMock = require('fetch-mock').sandbox();
const nodeFetch = require('node-fetch');
nodeFetch.default = fetchMock;
and fetchMock.mockReset(); with fetchMock.restore();
I've the following code and I need to mock the connection.execute() function as this belongs to the third-party lib snowflake-sdk and is't not part of my unit test:
import * as SnowflakeSDK from 'snowflake-sdk';
import { Injectable } from '#nestjs/common';
#Injectable()
export class SnowflakeClient {
export(bucket: string, filename: string, query: string) {
return new Promise((resolve, reject) => {
const connection = this.getConnection();
const command = `COPY INTO '${bucket}${filename}' FROM (${query})`;
connection.execute({
sqlText: command,
complete: function (err) {
try {
if (err) {
return reject(err);
} else {
return resolve(true);
}
} finally {
connection.destroy(function (err) {
if (err) {
console.error('Unable to disconnect: ' + err.message);
}
});
}
},
});
});
}
getConnection() {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
}
}
So I've created the following unit test:
import { SnowflakeClient } from 'src/snowflake/snowflake-client';
import { Test } from '#nestjs/testing';
describe('SnowflakeClient', () => {
let snowflakeClient: SnowflakeClient;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [SnowflakeClient],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
describe('export', () => {
it('should export a SQL query', async () => {
const connectionMock = jest.fn().mockImplementation(() => ({
execute: function (bucket: string, filename: string, sql: string) {
console.log(`${bucket}${filename}: ${sql}`);
},
}));
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(connectionMock);
const bucket = 's3://bucketName';
const filename = '/reports/test.csv';
const sql = 'select * from customers limit 100';
await expect(await snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
}, 10000);
});
});
But the connectionMock.execute isn't being called correctly as I'm getting the following error:
FAIL src/snowflake/snowflake-client.spec.ts (13.518 s)
SnowflakeClient
export
✕ should export a SQL query (10018 ms)
● SnowflakeClient › export › should export a SQL query
thrown: "Exceeded timeout of 10000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
14 |
15 | describe('export', () => {
> 16 | it('should export a SQL query', async () => {
| ^
17 | const connectionMock = jest.fn().mockImplementation(() => ({
18 | execute: function (bucket: string, filename: string, sql: string) {
19 | console.log(`${bucket}${filename}: ${sql}`);
at src/snowflake/snowflake-client.spec.ts:16:5
at src/snowflake/snowflake-client.spec.ts:15:3
at Object.<anonymous> (src/snowflake/snowflake-client.spec.ts:4:1)
I would like to test the SnowflakeClient.export() method but I need to mock the snowflake-sdk module as it isn't part of my code.
Anybody knows what I'm doing wrong?
When using sdk, it's better to pass them through the injection system of NestJs with a custom provider:
//////////////////////////////////
// 1. Provide the sdk in the module.
//////////////////////////////////
const SnowflakeConnectionProvider = {
provide: 'SNOWFLAKE_CONNECTION',
useFactory: () => {
const connection = SnowflakeSDK.createConnection({
account: process.env.SNOWFLAKE_HOST!,
username: process.env.SNOWFLAKE_USERNAME!,
password: process.env.SNOWFLAKE_PASSWORD!,
role: process.env.SNOWFLAKE_ROLE!,
warehouse: process.env.SNOWFLAKE_WAREHOUSE!,
database: process.env.SNOWFLAKE_DATABASE!,
schema: process.env.SNOWFLAKE_SCHEMA!,
});
connection.connect(function (err: Error): void {
if (err) {
throw err;
}
});
return connection;
},
inject: [],
}
#Module({
providers: [
SnowflakeClient,
SnowflakeConnectionProvider,
]
})
export class SnowflakeModule {}
//////////////////////////////////
// 2. Inject the connection into your `SnowflakeClient`
//////////////////////////////////
#Injectable()
export class SnowflakeClient {
constructor(#Inject('SNOWFLAKE_CONNECTION') private readonly connection: SnowflakeConnection) {}
// ...
}
//////////////////////////////////
// 3. Mock the dependency in tests
//////////////////////////////////
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: 'SNOWFLAKE_CONNECTION',
useValue: { /* Mock */},
},
SnowflakeClient,
],
}).compile();
snowflakeClient = moduleRef.get<SnowflakeClient>(SnowflakeClient);
});
You will have a much better control over the tests.
So I got this fixed doing this (but I'm not sure it's the best way):
const connectionMock = jest.createMockFromModule<Connection>('snowflake-sdk');
connectionMock.execute = jest.fn().mockImplementation(() => {
return Promise.resolve(null);
});
jest.spyOn(snowflakeClient, 'getConnection').mockImplementation(() => {
return connectionMock;
});
expect(snowflakeClient.export(bucket, filename, sql)).resolves.not.toThrow();
Let me know your thoughts :)
This is my nodejs typescript class and written jest unit test for isHealthy() public method.
Test coverage shows that this.pingCheck() then block, catch and last return statement are not covered.
Please advise.
Can we do unit test for pingCheck private method ?
This my class
import { HttpService, Injectable } from '#nestjs/common';
import { DependencyUtlilizationService } from '../dependency-utlilization/dependency-utlilization.service';
import { ComponentType } from '../enums/component-type.enum';
import { HealthStatus } from '../enums/health-status.enum';
import { ComponentHealthCheckResult } from '../interfaces/component-health-check-result.interface';
import { ApiHealthCheckOptions } from './interfaces/api-health-check-options.interface';
#Injectable()
export class ApiHealthIndicator {
private healthIndicatorResponse: {
[key: string]: ComponentHealthCheckResult;
};
constructor(
private readonly httpService: HttpService,
private readonly dependencyUtilizationService: DependencyUtlilizationService,
) {
this.healthIndicatorResponse = {};
}
private async pingCheck(api: ApiHealthCheckOptions): Promise<boolean> {
let result = this.dependencyUtilizationService.isRecentlyUsed(api.key);
if (result) {
await this.httpService.request({ url: api.url }).subscribe(() => {
return true;
});
}
return false;
}
async isHealthy(
listOfAPIs: ApiHealthCheckOptions[],
): Promise<{ [key: string]: ComponentHealthCheckResult }> {
for (const api of listOfAPIs) {
const apiHealthStatus = {
status: HealthStatus.fail,
type: ComponentType.url,
componentId: api.key,
description: `Health Status of ${api.url} is: fail`,
time: Date.now(),
output: '',
links: {},
};
await this.pingCheck(api)
.then(response => {
apiHealthStatus.status = HealthStatus.pass;
apiHealthStatus.description = `Health Status of ${api.url} is: pass`;
this.healthIndicatorResponse[api.key] = apiHealthStatus;
})
.catch(rejected => {
this.healthIndicatorResponse[api.key] = apiHealthStatus;
});
}
return this.healthIndicatorResponse;
}
}
This is my unit test code.
I get the following error when I run npm run test
(node:7876) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'status' of undefined
(node:7876) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 6)
import { HttpService } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { DependencyUtlilizationService } from '../dependency-utlilization/dependency-utlilization.service';
import { ApiHealthIndicator } from './api-health-indicator';
import { ApiHealthCheckOptions } from './interfaces/api-health-check-options.interface';
import { HealthStatus } from '../enums/health-status.enum';
describe('ApiHealthIndicator', () => {
let apiHealthIndicator: ApiHealthIndicator;
let httpService: HttpService;
let dependencyUtlilizationService: DependencyUtlilizationService;
let dnsList: [{ key: 'domain_api'; url: 'http://localhost:3001' }];
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ApiHealthIndicator,
{
provide: HttpService,
useValue: new HttpService(),
},
{
provide: DependencyUtlilizationService,
useValue: new DependencyUtlilizationService(),
},
],
}).compile();
apiHealthIndicator = module.get<ApiHealthIndicator>(ApiHealthIndicator);
httpService = module.get<HttpService>(HttpService);
dependencyUtlilizationService = module.get<DependencyUtlilizationService>(
DependencyUtlilizationService,
);
});
it('should be defined', () => {
expect(apiHealthIndicator).toBeDefined();
});
it('isHealthy should return status as true when pingCheck return true', () => {
jest
.spyOn(dependencyUtlilizationService, 'isRecentlyUsed')
.mockReturnValue(true);
const result = apiHealthIndicator.isHealthy(dnsList);
result.then(response =>
expect(response['domain_api'].status).toBe(HealthStatus.pass),
);
});
it('isHealthy should return status as false when pingCheck return false', () => {
jest
.spyOn(dependencyUtlilizationService, 'isRecentlyUsed')
.mockReturnValue(false);
jest.spyOn(httpService, 'request').mockImplementation(config => {
throw new Error('could not call api');
});
const result = apiHealthIndicator.isHealthy(dnsList);
result
.then(response => {
expect(response['domain_api'].status).toBe(HealthStatus.fail);
})
.catch(reject => {
expect(reject['domain_api'].status).toBe(HealthStatus.fail);
});
});
});
Looks like you should define the status before initialize the unit test, try to grab some more logs using console.log and for the second test, added catch block to make sure you're grabing the failures
This is nodejs typescript class to execute query or getConnection using oracledb npm.
I could not mock oraclebd getConnection in jest unit test.
How to write unit test for executeQuery. It shows test coverage to be done on finally block all statement.
I written unit test but
import { Inject, Injectable } from '#nestjs/common';
import * as oracledb from 'oracledb';
import { ORACLE_DB_OPTIONS } from '../constant';
import { OracleDBOptions } from '../interface';
import { PromInstanceCounter, PromMethodCounter } from '#digikare/nestjs-prom';
#PromInstanceCounter
#Injectable()
export class OracleDBService {
constructor(
#Inject(ORACLE_DB_OPTIONS)
private readonly oracleDbOptions: OracleDBOptions[],
) { }
#PromMethodCounter()
async executeQuery(
dbName: string,
query: string,
bindValue: any[] = [],
) {
let localConnection: any;
try {
const oracleDbOption = this.oracleDbOptions.filter(
x => x.name === dbName,
);
if (!oracleDbOption) {
throw Error('Connection string does not exist');
}
localConnection = await oracledb.getConnection({
user: oracleDbOption[0].user,
password: oracleDbOption[0].password,
connectString: oracleDbOption[0].connectString,
});
const result = await localConnection.execute(query, bindValue);
await localConnection.close();
return result;
} finally {
if (localConnection) {
try {
await localConnection.close();
} catch (err) {
console.log('Error when closing the database connection: ', err);
}
}
}
}
#PromMethodCounter()
async getConnection(dbName: string) {
const oracleDbOption = this.oracleDbOptions.filter(x => x.name === dbName);
if (!oracleDbOption) {
throw Error('Connection string does not exist');
}
const localConnection = await oracledb.getConnection({
user: oracleDbOption[0].user,
password: oracleDbOption[0].password,
connectString: oracleDbOption[0].connectString,
});
return localConnection;
}
}
Written Unit Test for getConnection() method.
describe('OracleDbService', () => {
let oracleDBService: OracleDBService;
let oracleDBOptions = [
{
name: 'rfdest',
user: 'rfdest',
password: 'rfdest',
connectString: 'alsyntstb.ohlogistics.com:1521/syntstb',
},
{
name: 'DOCGENDEV',
user: 'DOCGENDEV',
password: 'DocGen$dev18',
connectString: 'alsynwebd.ohlogistics.com/synwebd_maint',
},
];
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
OracleDBService,
{
provide: ORACLE_DB_OPTIONS,
useValue: oracleDBOptions,
},
],
}).compile();
oracleDBService = module.get<OracleDBService>(OracleDBService);
});
it('should be defined', () => {
expect(oracleDBService).toBeDefined();
});
it('getConnection success', () => {
jest.fn(getConnection).mockReturnValue({
execute: function() {},
close: function() {},
});
oracleDBService.getConnection('rfdest').then(resolve => {
expect(resolve).not.toBeNull();
});
});
it('getConnection throw error', () => {
jest.fn(getConnection).mockReturnValue({
execute: function() {},
close: function() {},
});
oracleDBService
.getConnection('')
.then(resolve => {
expect(resolve).not.toBeNull();
})
.catch(reject => {
expect(reject).not.toBeNull();
});
});
});