NestJs unit test crashes due to decorators - jestjs

I have a NestJS Jest-based unit test that keep crashing due to some parameter-scoped decorators somewhere in the import tree, for example, in this case, I had some methods whose first parameter had a #Args decorator from Graphql:
async getUserById(#((0, _graphql.Args)('id'))
^
SyntaxError: Invalid or unexpected token
If I try to enable babel-plugin-parameter-decorator in babel.config, it starts breaking other things, for example, it stops recognizing #Prop types correctly on my schemas, or throws a message-less "TypeError" when I try to reference my schema's .name property like here:
// my.service.ts:
#Injectable
export class MyService {
private logger = new Logger(MyService.name)
constructor(
#InjectModel(MyModel.name) private myModel: Model<MyModel>,
) {
}
}
Any idea how I should get jest to work?

as jay-mcdoniel hinted, that was the problem. I was not using ts-jest. Had to add to package.json's jest section:
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},

Related

Cannot find module when using type from another module in class-validator

I'm using typescript on both frontend and backend, so I wanted to create a "shared types" package for them. For the backend I'm using nest.js and I recently ran into an issue with the class-validator package.
In my shared types package I created the following enum-like type (since enums itself don't seem to be working if they are being used from a node module):
export const MealTypes = {
BREAKFAST: 'Breakfast',
LUNCH: 'Lunch',
DINNER: 'Dinner',
SNACK: 'Snack'
} as const;
export type ObjectValues<T> = T[keyof T];
export type MealType = ObjectValues<typeof MealTypes>;
I've installed the module locally using npm i and I'm able to import the type in my backend like this:
import { MealType, MealTypes } from '#r3xc1/shared-types';
Since I am not able to use this constant for the IsEnum class validator, I wrote my own:
#ValidatorConstraint({ name: 'CheckEnum', async: false })
export class CheckEnumValidator implements ValidatorConstraintInterface {
validate(value: string | number, validationArguments: ValidationArguments) {
return Object.values(validationArguments.constraints[0]).includes(value);
}
defaultMessage(args: ValidationArguments) {
return `Must be of type XYZ`;
}
}
and then I'm using it in a DTO class like this:
export class CreateMealDTO {
#Validate(CheckEnumValidator, [MealTypes])
#IsNotEmpty()
meal_type: MealType;
}
But as soon as I add the #Validate(...) I get the following error on start:
Error: Cannot find module '#r3xc1/shared-types'
It only does this, if I am passing a type that has been imported from a node module into a validator. It also happens with other validators like IsEnum.
I'm not really sure why this error is happening and I appreciate any hints or help!

Find front end domain outside a NestJS Controller

Similar to this, I am attempting to get the #Headers('origin') property inside a service which does not have its own controller and called to build email links for various other modules. Since the links are shared by email the front end domain triggering the call has to be identified which is done as follows :
import { Headers, Injectable } from '#nestjs/common';
// ... Other code specific imports
#Injectable()
export class LinkService {
constructor(#Headers('origin') private readonly origin: string) {}
createLink1(LinkId1: string): string {
return `${this.origin}/linkpath1/${LinkId1}`;
}
createLink2(LinkId2: string): string {
return `${this.origin}/linkpath2/${LinkId2}`;
}
// ... Similar follows
}
npm start leads to :
[Nest] 25971 - [ExceptionHandler] Nest can't resolve dependencies of the LinkService (?). Please make sure that the argument String at index [0] is available in the LinkModule context.
Potential solutions:
- If String is a provider, is it part of the current LinkModule?
- If String is exported from a separate #Module, is that module imported within LinkModule?
#Module({
imports: [ /* the Module containing String */ ]
})
+0ms
Error: Nest can't resolve dependencies of the LinkService (?). Please make sure that the argument String at index [0] is available in the LinkModule context.
Potential solutions:
- If String is a provider, is it part of the current LinkModule?
- If String is exported from a separate #Module, is that module imported within LinkModule?
#Module({
imports: [ /* the Module containing String */ ]
})
at Injector.lookupComponentInParentModules (/home/batfan47/graviti/check/graviti-api/node_modules/#nestjs/core/injector/injector.js:188:19)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async Injector.resolveComponentInstance (/home/batfan47/graviti/check/graviti-api/node_modules/#nestjs/core/injector/injector.js:144:33)
at async resolveParam (/home/batfan47/graviti/check/graviti-api/node_modules/#nestjs/core/injector/injector.js:98:38)
Would be great to know what would be the best way to go about this, so that the front end domain is available to me as required even in other parts of the code.
that #Headers('origin') on constructor just doens't make any sense. It must be in some method of your controller.
If you want the request object in some service, you could leverage on request scoped provider. Then you can retrieve the headers from it.

Proper way to manually instantiate Nest.js providers

I think I might be misunderstanding Nest.js's IoC container, or maybe DI as a whole.
I have a class, JSONDatabase, that I want to instantiate myself based on some config value (can either be JSON or SQL).
My DatabaseService provider:
constructor(common: CommonService, logger: LoggerService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase());
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase());
} else {
logger.error('Unknown database type.');
}
}
My JSONDatabase class:
export class JSONDatabase implements IDatabase {
dbType = DatabaseType.JSON;
constructor(logger: LoggerService, io: IOService) {
logger.log(`Doing something...`)
}
}
However, the problem with this is that if I want my JSONDatabase to take advantage of injection, ie. it requires both IOService and LoggerService, I need to add the parameters from the DatabaseService constructor rather than inject them through Nest's IoC containers.
Expected 2 arguments, but got 0 [ts(2554)]
json.database.ts(7, 15): An argument for 'logger' was not provided.
Is this the proper way to do this? I feel like manually passing these references through is incorrect, and I should use Nest's custom providers, however, I don't really understand the Nest docs on this subject. I essentially want to be able to new JSONDatabase() without having to pass in references into the constructor and have the Nest.js IoC container inject the existing singletons already (runtime dependency injection?).
I might be completely off base with my thinking here, but I haven't used Nest all that much, so I'm mostly working off of instinct. Any help is appreciated.
The issue you have right now is because you are instantiating JSONDatabase manually when you call new JSONDatabase() not leveraging the DI provided by NestJS. Since the constructor expects 2 arguments (LoggerService, and IOService) and you are providing none, it fails with the message
Expected 2 arguments, but got 0 [ts(2554)]
I think depending on your use case you can try a couple of different options
If you fetch your configuration on startup and set the database once in the application lifetime you can use use a Custom provider with the useFactory syntax.
const providers = [
{
provide: DatabaseService,
useFactory: (logger: LoggerService, io: IOService, config: YourConfigService): IDatabase => {
if (config.databaseType === DatabaseType.JSON) {
return new JSONDatabase(logger, io);
} else if (databaseType === DatabaseType.SQL) {
return new SQLDatabase(logger, io);
} else {
logger.error('Unknown database type.');
}
},
inject: [LoggerService, IOService, YourConfigService]
},
];
#Module({
providers,
exports: providers
})
export class YourModule {}
If you have LoggerService, IOService and YourConfigurationService annotated with #Injectable() NestJS will inject them in the useFactory context. There you can check the databaseType and manually instantiate the correct IDatabase implementation. The drawback with this approach is that you can't easily change the database in runtime. (This might work just fine for your use case)
You can use strategy/factory pattern to get the correct implementation based on a type. Let say you have a method that saves to different databases based on an specific parameter.
#Injectable()
export class SomeService {
constructor(private readonly databaseFactory: DatabaseFactory){}
method(objectToSave: Object, type: DatabaseType) {
databaseFactory.getService(type).save(objectToSave);
}
}
#Injectable()
export class DatabaseFactory {
constructor(private readonly moduleRef: ModuleRef) {}
getService(type: DatabaseType): IDatabase {
this.moduleRef.get(`${type}Database`);
}
}
The idea of the code above is, based on the database type, get the correct singleton from NestJS scope. This way it's easy to add a new database if you want - just add a new type and it's implementation. (and your code can handle multiple databases at the same time!)
I also believe you can simply pass the already injected LoggerService and IOService to the DatabasesService you create manually (You would need to add IOService as a dependency of DatabaseServce
#Injectable()
export class DatabaseService {
constructor(common: CommonService, logger: LoggerService, ioService: IOService) {
// eslint-disable-next-line prettier/prettier
const databaseType: DatabaseType = common.serverConfig.dbType as DatabaseType;
if (databaseType === DatabaseType.JSON) {
this.loadDatabase<JSONDatabase>(new JSONDatabase(logger, ioService));
} else if (databaseType === DatabaseType.SQL) {
this.loadDatabase<SQLDatabase>(new SQLDatabase(logger, ioService));
} else {
logger.error('Unknown database type.');
}
}
}

How do you use a fallback exception filter in NestJs

I'm new to NestJs and I created a fallback exception filter, and now I would like to know how to use it. In other words, how do I import it in my application?
Here's my fallback exception filter:
#Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
public catch(exception: HttpException, host: ArgumentsHost): any {
/* Some code here */
return response.status(statusCode).json({
status: statusCode,
datetime: new Date(),
createdBy: "HttpExceptionFilter",
errorMessage: exception.message,
})
}
}
You'd need to bind the filter globally to be the fallback. You can do this one of two ways
With a custom provider in any module. Add this to the module's providers array
{
provide: APP_FILTER,
useClass: HttpExceptionFilter
}
This will still take effect in e2e tests, as it's part of the module definition
By using useGlobalFilters in your bootstrap method like so
app.useGlobalFilters(new HttpExceptionFilter());
This will not take effect in your e2e tests, so you'll need to bind it in those too, if you want the same functionality.
Just add this in your main.ts and it should work fine:
app.useGlobalFilters(new FallbackExceptionFilter();

#Inject services in other service - Nest can't resolve dependencies

I'm trying to #Inject two services into a NestJS service and am getting
Nest can't resolve dependencies of the MainServiceImpl (?,
Symbol(MainDao)). Please make sure that the argument at index [0] is
available in the MainModule context
Here is the service:
#Injectable()
export class MainServiceImpl implements MainService {
constructor(
#Inject(TYPES.PublishSubscriptionService) private publishSubscriptionService: PublishSubscriptionService,
#Inject(TYPES.MainDao) private mainDao: MainDao
) {}
Now if I switch the order, it's always the second injected service that gets the error.
TYPES is an object of Symbols
const TYPES = {
PublishSubscriptionService: Symbol('PublishSubscriptionService'),
MainDao: Symbol('MainDao'),
};
export default TYPES;
Here is the index.ts which is using barreling
// start:ng42.barrel
export * from './main.dao.mongo-impl';
export * from './main.dao';
export * from './main';
export * from './main.schema';
export * from './main.service.impl';
export * from './main.service';
// end:ng42.barrel
What am I missing?
Closed NestJS issue with no resolution
NestJS doc on #Global modules
I can't say for sure without seeing your MainServieImplModule or whatever the module that contains this service is called, but if I had to guess, you are not declaring your providers to Nest correctly for these two services. You'll need to create a custom provider like so
#Module({
provides: [
{
provide: TYPES.PublishSubscriptionService,
useClass: PublishSubscriptionService,
},
{
provide: TYPES.MainDao,
useClass: MainDao
},
MainServiceImpl
]
})
export class MainServiceImplModule {}
If however, those providers are part of a different module you will need to make sure that the providers are custom (as above), but are also exported so they can be imported and used in a different module. Without seeing more of your code, the question is not possible to answer, but I think this should give you the direction you need. If not, please edit your question and add more information.
I don't know what TYPES is, you can use #Inject(PublishSubscriptionService) or you can use simply:
constructor(
private publishSubscriptionService: PublishSubscriptionService,
private mainDao: MainDao
) {}

Resources