I'm working with a home grown mono repo structure in with NestJS and legacy code. The NestJS parts of the monorepo depend on a common folder in the root that is imported into each Nest Project via "commonPackage":"file:../common" in the package.json file.
The issue I'm experiencing is that the common folders install of #nestjs/config is conflicting with the consuming project's install of the same package. I've been using a workaround to import the necessary code from commonPackage/node_modules/#nestjs/config however that is using the common folder's .env file instead of the consuming project's .env
I have no runtime dependencies in the common package, and I've set #nestjs/config as a peer dependency with a version flag of ^1 however, when attempting to import the consuming project's config
(i.e. import {ConfigService} from '#nestjs/config'; and not the above) service I get an error about an internal property not matching in the spec like below.
src/app.module.ts:16:26 - error TS2345: Argument of type '(config: ConfigService) => ConnectionOptions' is not assignable to parameter of type '(config: ConfigService<Record<string, unknown>>) => ConnectionOptions'.
Types of parameters 'config' and 'config' are incompatible.
Type 'ConfigService<Record<string, unknown>>' is not assignable to type 'ConfigService<Record<string, unknown>, false>'.
Types have separate declarations of a private property 'internalConfig'.
16 MysqlModule.register(sqlConfig),
~~~~~~~~~
[3:47:23 PM] Found 1 error. Watching for file changes.
The workaround solution I worked out was to simply export the config service I was using for my internal module, however I now think it should be possible to pass in the config service when registering the module.
so My current solution is to export the config service from the module that uses it in the common repo:
export declare type SpecConfig = ConfigService;
Use this when defining factories that get put into that module.
*** One caveat is that you will not be able to specify a custom config file with this method as the import is handled in your register logic.***
another option is to add the config service as a dependency to the module registration, but I have yet to work that out.
for example this is my Dynamic Module that was causing the issue.
import { DynamicModule, Module, Provider } from '#nestjs/common';
import { ConfigModule, ConfigService } from '#nestjs/config';
import * as mysql from 'mysql2';
import { MysqlConnectionService } from './mysql-connection/mysql-connection.service';
#Module({})
export class MysqlModule {
static register(...options: MysqlModuleOptions[]): DynamicModule {
const providers: Provider<any>[] = options.map(({config: connectionConfig, name}) => {
const config = {
provide: `${name}-mysql-config`,
useFactory: connectionConfig,
inject: [ConfigService]
}
const provider = {
provide: `${name}-mysql`,
useFactory: (config: mysql.ConnectionOptions) => new MysqlConnectionService(config),
inject: [`${name}-mysql-config`],
}
return [config, provider];
}).reduce((acc, val) => acc.concat(val), [])
return {
module: MysqlModule,
imports: [ConfigModule.forRoot({isGlobal: true})],
providers: [...providers],
exports: [...providers]
}
}
}
export type MysqlConfigFunc = (config: ConfigService) => mysql.ConnectionOptions
export type MsqlConfigService = ConfigService;
export class MysqlModuleOptions {
name: string;
config: MysqlConfigFunc;
}
Related
A bit fuzzy title, but could not describe it more clearly.
I have a repository containing reusable functions and React components which we use in other React applications. We use jest & testing library to test our React applications. When testing the application the re-usable functions are mocked.
jest.mock('#myorg/reusable-fnandcomp', () => ({
// #ts-ignore
...jest.requireActual('#myorg/reusable-fnandcomp'),
submitEvent: jest.fn(),
}));
In the repo containing the reusable functions and components, the components make use of the same reusable functions. I import the functions using relative paths
import { submitEvent } from '../index';
Instead of
import { submitEvent } from '#myorg/reusable-fnandcomp';
Because the submitEvent cannot be imported if it is not published in the repository. (typical chicken-and-egg situation).
This works fine, except when the submitEvent needs to be mocked in the React application. Because the submitEvent in the reusable component is imported with ../index, the jest.mock in the test of the application does not match with #myorg/reusable-fnandcomp. Therefore the actual implementation of submitEvent is invoked instead of the mock.
In pseudo code;
reusable repo
../components/SomeComp.JSX
import { submitEvent } from '../index';
export const SomeComp = () => {
submitEvent()
}
../functions/submitEvent.TS
export const submitEvent = () => {
// do something here
}
React application
../tests/sometest.spec.JSX
import {
SomeComp,
submitEvent
} from '#myorg/reusable-fnandcomp';
jest.mock('#myorg/reusable-fnandcomp', () => ({
// #ts-ignore
...jest.requireActual('#myorg/reusable-fnandcomp'),
submitEvent: jest.fn(),
}));
render(<SomeComp/>)
expect(submitEvent).toHaveBeenCalled() // actual implementation called here
I understand why the actual implementation is invoked. A resolution would be by adding the #myorg/reusable-fnandcomp as a development dependency to the project. Then first publish this repository and then use import { submitEvent } from '#myorg/reusable-fnandcomp'; where i currently use import { submitEvent } from '../index'; and re-publish this. But this seems to be a bit ugly. Are there suggestions how to resolve this better/properly ?
Comment
From my personal view the expect(submitEvent).toHaveBeenCalled() should not be part of the test of the React component. It should have been tested in the repository containing the function. But since it is an existing repository which i got under control i was wondering if there is not a better approach.
I've been getting this error all day long:
Nest can't resolve dependencies of the ClubsService (ClubsApiService, AuthApiService, ClubFollowersRepo, ClubsRepo, ClubPrivacyRepo, ?). Please make sure that the argument DatabaseConnection at index [5] is available in the ClubsModule context.
Potential solutions:
- If DatabaseConnection is a provider, is it part of the current ClubsModule?
- If DatabaseConnection is exported from a separate #Module, is that module imported within ClubsModule?
#Module({
imports: [ /* the Module containing DatabaseConnection */ ]
})
I figured that the problem is, that I have not mocked the Mongo DB connection. The error is quite clear, the #InjectConnection in ClubsService should be mocked (see below).
ClubsService:
#Injectable()
export class ClubsService {
constructor(
private readonly clubsApiService: ClubsApiService,
private readonly authApiService: AuthApiService,
private readonly clubFollowersRepo: ClubFollowersRepo,
private readonly clubsRepo: ClubsRepo,
private readonly clubPrivacyRepo: ClubPrivacyRepo,
#InjectConnection() private readonly connection: Connection, // <--- THIS GUY
) {}
// ...
}
The problem is that the test file I am executing is in a different module than where ClubsService is. And so in the different module (let's call it YModule), I have this piece of code:
YModule:
import { getConnectionToken } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { Connection, connect } from 'mongoose';
describe('YService.spec.ts in YModule', () => {
beforeAll(async () => {
mongod = await MongoMemoryServer.create();
const uri = mongod.getUri();
mongoConnection = (await connect(uri)).connection;
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
// ...
],
imports: [ClubsModule], // <--- ClubsModule is not provider, but imported module
})
.overrideProvider(getConnectionToken())
.useValue(mongoConnection)
.compile();
});
});
This approach with getConnectionToken() won't work as I have to mock a connection coming from the imported ClubsModule, not a provider.
How would you mock a connection injected in a different module that you imported?
Thanks a lot! :)
As Jay McDoniel mentioned in the post comment, you should not import modules in your unit testing file but mock the needed dependencies instead. Why is that? Consider the example from the question above:
ClubsModule has the connection dependency that can be replaced with an in-memory database server, that's true, but this should be done within the ClubsModule itself (clubs folder), not outside in different modules.
What you really want to do outside ClubsModule, let's say, in the YModule (y folder), is to mock every provider ClubsModule exports that you use within the test file of YModule.
This makes sense as you should test ClubsModule specific dependencies only within its module and everywhere else just mock it.
I originally imported ClubsModule because I wanted to use the repository (a provider) that ClubsModule exports. But then I realized that I don't want to test the functionality of the repository's function, I already test them inside the ClubsModule, so there is no need to do that twice. Instead, it is a good idea to mock the repository instead.
Code Example:
y.service.spec.ts:
import { YService } from './y.service'; // <--- For illustration; Provider within YModule
import { ClubsRepo } from '../clubs/clubs.repo'; // <--- Import the real Repository Provider from different Module (ClubsModule)
describe('y.service.spec.ts in YModule', () => {
const clubsRepo = { // <--- Mocking ClubRepo's functions used within this test file
insertMany: () => Promise.resolve([]),
deleteMany: () => Promise.resolve(),
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ClubsRepo, // <--- The real Repository Provider from the import statement above
],
})
.overrideProvider(ClubsRepo) // <--- Overriding the Repository Provider from imports
.useValue(clubsRepo) // <--- Overriding to the mock 'clubsRepo' (const above)
.compile();
service = module.get<YService>(YService); // <--- For illustration; unlike ClubsRepo, this provider resides within this module
});
it('example', () => {
// ...
jest.spyOn(clubsRepo, 'insertMany'); // <--- Using "insertMany" from the mocked clubsRepo (the const) defined at the beginning
// ...
});
});
The reason for importing ClubsRepo to the test file y.service.spec.ts is because y.service.ts (the actual Provider in YModule) uses the functions of the ClubsRepo. In this case, don't forget importing ClubsModule in y.module.ts too.
y.module.ts:
import { ClubsModule } from '../clubs/clubs.module';
#Module({
imports: [
// ...
ClubsModule, // <--- Don't forget this line
// ...
],
providers: [
// ...
],
})
export class YModule {}
That's it, happy testing! :)
i'm new in NestJS and have some misunderstands with #liaoliaots/nestjs-redis(https://github.com/liaoliaots/nestjs-redis) package. For example, i have a guard with following constructor:
import { InjectRedis } from '#liaoliaots/nestjs-redis';
import { Redis } from 'ioredis';
#Injectable()
export class SomeGuard implements CanActivate {
constructor(#InjectRedis() redis: Redis) {}
...
}
and then i want that guard to be global:
//main.ts
...
app.useGlobalGuards(new SomeGuard(/* what??? */));
...
so thats a problem: what i need to pass? #InjectRedis makes weird things:)
thx for responding
Instead of app.useGlobalGuards, use this another way:
// ...
import { Module } from '#nestjs/common'
import { APP_GUARD } from '#nestjs/core'
#Module({
// ...
providers: [
{
provide: APP_GUARD,
useClass: SomeGuard,
},
],
})
export class AppModule {}
is cleaner and helps you avoid polluting your boostrap funcion. Also, it lets Nest resolves that Redis dependency. Otherwise you'll need to get this dependency and pass it to new SomeGuard using
const redis = app.get(getRedisToken())
https://docs.nestjs.com/guards#binding-guards
I'm using NestJs to create a couple of applications and I want to move the code from a NestInterceptor for an external NPM Package so I can use the same interceptor in multiple applications.
The problem is that the same code that works when used "locally" just stop working when moved to the external package.
Here's the code for the interceptor:
import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from '#nestjs/common'
import { map } from 'rxjs/operators'
import { getManager } from 'typeorm'
import jwt_decode from 'jwt-decode'
#Injectable()
export class MyInterceptor implements NestInterceptor {
entity: any
constructor(entity: any) {
this.entity = entity
}
async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
const request = context.switchToHttp().getRequest()
const repository = getManager().getRepository(this.entity)
return next.handle().pipe(map((data) => data))
}
}
Here's a given controller:
import { myInterceptor } from "../src/interceptors/interceptor.ts";
#UseInterceptors(new CompanyIdInterceptor(User))
export class UserController {
}
This works fine, but if a move the file to an external NPM package and import from it like this:
import { myInterceptor } from "mynpmpackage";
I get the following error:
[Nest] 24065 - 04/18/2019, 10:04 AM [ExceptionsHandler] Connection "default" was not found. +26114ms
ConnectionNotFoundError: Connection "default" was not found.
at new ConnectionNotFoundError (/home/andre/Services/npm-sdk/src/error/ConnectionNotFoundError.ts:8:9)
at ConnectionManager.get (/home/andre/Services/npm-sdk/src/connection/ConnectionManager.ts:40:19)
Any ideas, on what causes this and how to solve it?
This might not be your problem exactly, but I had a similar problem when moving things to external packages with TypeORM. Make sure all packages from parent project are using the same version of the TypeORM package.
In my case, using yarn why typeorm showed me two different versions were being installed. One of them was used to register the entities, while the framework connected to the SQL database using another version, generating this clash.
Check your versions using yarn why [pkg-name] or if you're using NPM, try npx npm-why [pkg-name] or install globally from https://www.npmjs.com/package/npm-why.
After verifying TypeOrm versions is same in both the packages i.e- external package and consumer repository as mentioned by #Luís Brito still issue persist then issue could be-
Basically when we create an external package - TypeORM tries to get the "default" connection option, but If not found then throws an error:
ConnectionNotFoundError: Connection "default" was not found.
We can solve this issue by doing some kind of sanity check before establishing a connection - luckily we have .has() method on getConnectionManager().
import { Connection, getConnectionManager, getConnectionOptions,
createConnection, getConnection, QueryRunner } from 'typeorm';
async init() {
let connection: Connection;
let queryRunner: QueryRunner;
if (!getConnectionManager().has('default')) {
const connectionOptions = await getConnectionOptions();
connection = await createConnection(connectionOptions);
} else {
connection = getConnection();
}
queryRunner = connection.createQueryRunner();
}
Above is a quick code-snippet which was the actual root cause for this issue but If you are interested to see complete working repositories (different example) -
External NPM Package :
Git Repo : git-unit-of-work (specific file- src/providers/typeorm/typeorm-uow.ts)
Published in NPM : npm-unit-of-work
Consumer of above package : nest-typeorm-postgre (specific files- package.json, src/countries/countries.service.ts & countries.module.ts)
I am creating an angular 4 node package for a logging service that we have created. Is it possible to use the angular cli environment variables to set the default path for the service? If so, how should the path be defined for the environment file?
I am following these instructions for creating the node package:
https://medium.com/#cyrilletuzi/how-to-build-and-publish-an-angular-module-7ad19c0b4464
I followed these examples for setting up environment variables:
http://tattoocoder.com/angular-cli-using-the-environment-option/
https://medium.com/beautiful-angular/angular-2-and-environment-variables-59c57ba643be
You can do that using the forRoot methodology explained in the Angular docs here.
Inside of your external Angular library modify the main exported module to have this structure:
export class MyLibraryModule {
static forRoot(config: configData): ModuleWithProviders {
return {
ngModule: MyLibraryModule,
providers: [
{provide: AppConfigModel, useValue: config }
]
};
}
constructor(#Optional() config: AppConfigModel) {
if (config) { /* Save config data */ }
}
}
export class AppConfigModel {
defaultPath: string;
}
When you import this module into your application, use the forRoot static method and pass in the config data.
imports: [
MyLibraryModule.forRoot({ defaultPath: environment.defaultPath }),
]
On a side note, I use this library for creating my Angular npm modules. It simplifies the process quite a bit.