How to use service/module inside of another module? - javascript

In the main.ts file of my nestJS application I would like to add some fixture data to my database if the app is starting in dev mode:
const app = await NestFactory.create(AppModule)
await app.listen(port, async () => {
if (!production) {
const User = this.db.collection('user')
await User.deleteMany({})
await User.insertMany(user)
}
})
Of course this is not working, as I do not have db at this time.
I'm defining the database connection in a module and this is how my database.module.ts looks like.
Is it possible to put the fixtures parts (drop database and add fixture data) in the database.module? The reason why I think I have to add it to the main.ts is that I need to run it on application start, not at every db connection.
import { Module, Inject } from '#nestjs/common'
import { MongoClient, Db } from 'mongodb'
#Module({
providers: [
{
provide: 'DATABASE_CLIENT',
useFactory: () => ({ client: null })
},
{
provide: 'DATABASE_CONNECTION',
inject: ['DATABASE_CLIENT'],
useFactory: async (dbClient): Promise<Db> => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017')
dbClient.client = client
const db = client.db('database')
return db
} catch (error) {
console.error(error)
throw error
}
}
}
],
exports: ['DATABASE_CONNECTION', 'DATABASE_CLIENT']
})
export class DatabaseModule {
constructor(#Inject('DATABASE_CLIENT') private dbClient) {}
async onModuleDestroy() {
await this.dbClient.client.close()
}
}
With that I can use my db in every other module, but that doesn't help me to get db connection at start up of the application:
import { Module } from '#nestjs/common'
import { MyService } from './my.service'
import { MyResolvers } from './my.resolvers'
import { DatabaseModule } from '../database.module'
#Module({
imports: [DatabaseModule],
providers: [MyService, MyResolvers]
})
export class MyModule {}

If you're looking to get the database client in your main.ts you need to get into the client from the container system nest has. You can do that with the following line, used before your app.listen()
const db = app.get('DATABASE_CLIENT', { strict: false })
And then instead of this.db you'll just use db.client.collection()

Related

How to use redis client in nestjs?

I have a microservice in NestJs, here is the main.ts file:
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
}
})
await app.listen();
console.log('Redis is running');
}
bootstrap();
I want to make calls to redis db from the service:
import { createClient } from '#redis/client';
async createWorkspaceShareCode(data: GetWorkspaceShareCodeMessage) {
const client = createClient({
url: 'redis://localhost:6379'
});
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
return value;
}
But that's not what I want, here are the issues:
every time I make request, it creates new connection
I need to write this code in each function to connect to redis (even though I'm already connected in main.ts file)
Here is the question: how can I use some kind of RedisService in my NestJs microservice?
I have already tried installing redis and nestjs-redis libraries, but the first one creates the issue described above and the second one just throws me an error: https://github.com/skunight/nestjs-redis/issues/97
What I would do is keep your createMicroservice as you have it as it'll be encessary for a Redis based microservice server. Then you can create a module that exports a redis service like so:
#Module({
providers: [
{
provide: 'REDIS_OPTIONS',
useValue: {
url: 'redis://localhost:6379'
}
},
{
inject: ['REDIS_OPTIONS']
provide: 'REDIS_CLIENT',
useFactory: async (options: { url: string }) => {
const client = createClient(options);
await client.connect();
return client;
}
}
],
exports: ['REDIS_CLIENT'],
})
export class RedisModule {}
Now in the module that contains the service you want to use add RedisModule to the imports array and in the service you can do #Inject('REDIS_CLIENT') private readonly redis: Redis to inject the redis instance and use it with this.redis.get('key') and this.redis.set('key', value)

Jest testing a class that uses sqlite3; jest cannot find sqlite3/package.json?

I am trying to write unit tests of a class which utilizes sqlite3, using TypeScript.
The class itself can correctly utilize sqlite3, but when I try to test the functions which use sqlite3, I get the following error message:
Test suite failed to run
package.json does not exist at C:\...\node_modules\sqlite3\package.json
When I click that link, however, it opens up the package.json file. So it does, actually, exist.
A simplified version of the class under test is as follows:
import sqlite3 from "sqlite3";
import path from "path";
import fs from "fs";
export class DataManager {
database_path: string;
gamertags: { [key: string]: any };
api: object;
constructor() {
//Prepare database
this.database_path = path.join(__dirname, "..", "data", "DBName.db");
}
public mainloop = async () => {
//Setup database
this.setup_db(this.database_path);
};
setup_db = async (db_path: string) => {
//Create data directory if it doesn't exist
if (!fs.existsSync(path.join(__dirname, "..", "data"))) {
fs.mkdirSync(path.join(__dirname, "..", "data"));
}
//Get read/write connection to database
const db = new sqlite3.Database(db_path);
//Serialize context to make sure tables are created in order
db.serialize(() => {
//Create table_1
db.run(`sql_query...`);
//Create table_2
db.run(`sql_query...`);
});
};
}
And my unit tests:
import { DataManager } from "../../core/dataManager";
import { mocked } from "ts-jest/utils";
import fs from "fs";
import sqlite3 from "sqlite3";
jest.mock("fs");
jest.mock("sqlite3");
let manager: DataManager;
const fake_database_path: string = "fake/database/path";
describe("datamanager.setup_db", () => {
beforeEach(() => {
jest.resetAllMocks();
manager = new DataManager();
manager.database_path = fake_database_path;
});
it("should create a new database file if one does not exist", async () => {
//Arrange
mocked(fs).existsSync = jest.fn().mockReturnValue = false as any;
mocked(sqlite3).Database = jest.fn() as any;
//Act
await manager.setup_db(fake_database_path);
});
});
I'm just not sure what on earth is preventing sqlite3 from finding its own package.json. None of my other dependencies seem to cause a fuss, yet sqlite3 does.

Testing Service with Mongoose in NestJS

I am trying to test my LoggingService in NestJS and while I cannot see anything that is wrong with the test the error I am getting is Error: Cannot spy the save property because it is not a function; undefined given instead
The function being tested (trimmed for brevity):
#Injectable()
export class LoggingService {
constructor(
#InjectModel(LOGGING_AUTH_MODEL) private readonly loggingAuthModel: Model<IOpenApiAuthLogDocument>,
#InjectModel(LOGGING_EVENT_MODEL) private readonly loggingEventModel: Model<IOpenApiEventLogDocument>,
) {
}
async authLogging(req: Request, requestId: unknown, apiKey: string, statusCode: number, internalMsg: string) {
const authLog: IOpenApiAuthLog = {
///
}
await new this.loggingAuthModel(authLog).save();
}
}
This is pretty much my first NestJS test and as best I can tell this is the correct way to test it, considering the error is right at the end it seems about right.
describe('LoggingService', () => {
let service: LoggingService;
let mockLoggingAuthModel: IOpenApiAuthLogDocument;
let request;
beforeEach(async () => {
request = new JestRequest();
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggingService,
{
provide: getModelToken(LOGGING_AUTH_MODEL),
useValue: MockLoggingAuthModel,
},
{
provide: getModelToken(LOGGING_EVENT_MODEL),
useValue: MockLoggingEventModel,
},
],
}).compile();
service = module.get(LoggingService);
mockLoggingAuthModel = module.get(getModelToken(LOGGING_AUTH_MODEL));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('authLogging', async () => {
const reqId = 'mock-request-id';
const mockApiKey = 'mock-api-key';
const mockStatusCode = 200;
const mockInternalMessage = 'mock-message';
await service.authLogging(request, reqId, mockApiKey, mockStatusCode, mockInternalMessage);
const authSpy = jest.spyOn(mockLoggingAuthModel, 'save');
expect(authSpy).toBeCalled();
});
});
The mock Model:
class MockLoggingAuthModel {
constructor() {
}
public async save(): Promise<void> {
}
}
After much more googling I managed to find this testing examples Repo: https://github.com/jmcdo29/testing-nestjs which includes samples on Mongo and also suggest that using the this.model(data) complicates testing and one should rather use `this.model.create(data).
After making that change the tests are working as expected.
The issue comes from the fact that you pass a class to the TestingModule while telling it that it's a value.
Use useClass to create the TestingModule:
beforeEach(async () => {
request = new JestRequest();
const module: TestingModule = await Test.createTestingModule({
providers: [
LoggingService,
{
provide: getModelToken(LOGGING_AUTH_MODEL),
// Use useClass
useClass: mockLoggingAuthModel,
},
{
provide: getModelToken(LOGGING_EVENT_MODEL),
// Use useClass
useClass: MockLoggingEventModel,
},
],
}).compile();
service = module.get(LoggingService);
mockLoggingAuthModel = module.get(getModelToken(LOGGING_AUTH_MODEL));
});

Unable to mock elasticsearch service in nestjs

Current behavior
Nest can't resolve dependencies of the ElasticsearchService (?). Please make sure that the argument at index [0] is available in the ElasticsearchModule context.
Expected behavior
Create ElasticSearchService in test module
Minimal reproduction of the problem with instructions
import { Test, TestingModule } from '#nestjs/testing';
import { RepliesController } from './replies.controller';
import { ElasticsearchService, ElasticsearchModule } from '#nestjs/elasticsearch';
import { Client } from 'elasticsearch';
describe('Replies Controller', () => {
let module: TestingModule;
let elasticsearch: ElasticsearchService;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ElasticsearchModule],
controllers: [RepliesController],
components: [ElasticsearchService, {provide: Client, useValue: {}}],
}).compile();
elasticsearch = module.get<ElasticsearchService>(ElasticsearchService);
});
it('should be defined', () => {
const controller: RepliesController = module.get<RepliesController>(RepliesController);
expect(controller).toBeDefined();
});
});
Environment
[Nest Information]
elasticsearch version : 0.1.2
common version : 5.4.0
core version : 5.4.0
You need to override the providers using methods exposed by the test module instead of doing it in the components array:
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [ElasticsearchModule],
controllers: [RepliesController]
})
.overrideProvider(ElasticsearchService)
.useValue(/* your mock or whatever */)
.compile();
There are examples of this in the NestJs docs in the testing section.

Angular 2 JWT Unit Testing

My API calls are authenticated with JWT. I am trying to write code for a service method. All requests has this interceptor:
public interceptBefore(request: InterceptedRequest): InterceptedRequest {
// Do whatever with request: get info or edit it
this.slimLoadingBarService.start();
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.data.token) {
request.options.headers.append('Authorization', 'Bearer ' + currentUser.data.token);
}
return request;
}
Service method that I want to test:
getAll(page: number, pageSize: number, company: string): Observable<any> {
return this.http.get(`${this.conf.apiUrl}/jobs`)
.map((response: Response) => response.json());
}
Started the code for it:
import { MockBackend, MockConnection } from '#angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod } from '#angular/http';
import { JobListService } from './job-list.service';
import { inject, TestBed } from '#angular/core/testing/test_bed';
import { JOBLISTMOCK } from '../mocks/job-list.mock';
fdescribe('Service: JobListService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
JobListService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
},
]
});
});
it('should create a service', inject([JobListService], (service: JobListService) => {
expect(service).toBeTruthy();
}));
describe('getAll', () => {
it('should return jobs', inject([JobListService, MockBackend], (service: JobListService, backend: MockBackend) => {
let response = new ResponseOptions({
body: JSON.stringify(JOBLISTMOCK)
});
const baseResponse = new Response(response);
backend.connections.subscribe(
(c: MockConnection) => c.mockRespond(baseResponse)
);
return service.getAll(1, 10, '18').subscribe(data => {
expect(data).toEqual(JOBLISTMOCK);
});
}));
});
});
Do not know how to test it against the interceptor.
PS: As the tests are now, getting an error:
1) should create a service
JobListService
TypeError: null is not an object (evaluating 'this.platform.injector') in src/test.ts (line 83858)
_createCompilerAndModule#webpack:///~/#angular/core/testing/test_bed.js:254:0 <- src/test.ts:83858:44
2) should return jobs
JobListService getAll
TypeError: null is not an object (evaluating 'this.platform.injector') in src/test.ts (line 83858)
_createCompilerAndModule#webpack:///~/#angular/core/testing/test_bed.js:254:0 <- src/test.ts:83858:44
TypeError: null is not an object (evaluating 'this.platform.injector')
Generally you will get this error if you haven't initialized the test environment correctly. You could solve this problem by doing the following
import {
BrowserDynamicTestingModule, platformBrowserDynamicTesting
} from '#angular/platform-browser-dynamic/testing';
...
beforeAll(() => {
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
});
The thing about this though, is that it should only be called once for the entire test suite execution. So if you have it in every test file, then you need to reset it first in each file
beforeAll(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
});
Better than this though, is to not add it in each test file. If you look at the Angular docs for Webpack integration, in the testing section, you will see a file karma-test-shim.js. In this file is the recommended way to initialize the test environment
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('core-js/es7/reflect');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
var appContext = require.context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('#angular/core/testing');
var browser = require('#angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting());
You can see at the bottom where we make the same initialization call as above. You should add this file to the karma.conf.js file in the files array in the configuration. This is from the linked documentation above
files: [
{pattern: './config/karma-test-shim.js', watched: false}
],
preprocessors: {
'./config/karma-test-shim.js': ['webpack', 'sourcemap']
},

Categories

Resources