Tests fail when using #InjectRepository: Nest can't resolve Repository - javascript

I'm using NestJs with Typeorm, normal setup. UsersService gets the Typeorm Repository injected:
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>,
) {}
In the UsersModule:
#Module({
imports:[ TypeOrmModule.forFeature([User])],
controllers: [UsersController ],
providers: [UsersService]
})
Nothing special as you can see. But the auto-generated test for UsersService fails, no matter what I do:
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
I get the following error:
Nest can't resolve dependencies of the UsersService (?). Please
make sure that the argument UserRepository at index [0] is available
in the RootTestModule context.
The solutions on Stackoverflow that I found seem to be obsolete, or over-complicated. I understand that the problem stems from the usage of #InjectRepository.
What is the solution? I tried downloading other people's fairly-similar projects, and get the same error! Both with nest 8 and 7.

Nest can't resolve the dependency because you don't provide the repository in the testing module. If you're doing unit tests you probably want to mock the repository using a custom provider:
import { getRepositoryToken } from '#nestjs/typeorm';
describe('UsersService', () => {
let service: UsersService;
let repository: Repository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: {},
}
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
})
You can provide an object, a class or a factory function, more details in the doc: https://docs.nestjs.com/fundamentals/custom-providers
Then in your tests you can mock the methods of the repository this way:
jest.spyOn(repository, 'find').mockResolvedValueOnce([])
It's not the only way to mock, but that's a simple and standard one.

The docs are pretty clear on how to write tests when using #nestjs/typeorm: https://docs.nestjs.com/techniques/database#testing
There are a bunch of samples here as well: https://github.com/jmcdo29/testing-nestjs

So I managed to "solve" this myself. I should've mentioned perhaps that i didn't intend to do any mocking, but wanted the test to work "as is"(I prefer using a test-dedicated DB, rather than mocking units. Seems more realistic to me).
So it appears i kind of misunderstood, that every call to createTestingModule() needs to make sure all relevant dependencies are created, including stuff like ORM initialization, which is usually done in the AppModule(here i'm testing a service in UserModule..). So what I did in users.service.specs.ts:
const module: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forFeature([User]), TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: '',
database: 'postgres',
schema: 'test-db',
entities: [User],
synchronize: true,
}), TypeOrmModule.forFeature([User])],
providers: [UsersService],
}).compile();
Notice that I had to both create the TypeOrm connection, and register the entity.
Now I understand that each test suite is totally isolated, and therefore needs all relevant dependencies to be passed to it, even if in the "original" application this code is already imported in the root module.

Related

NestJS ConfigModule.forRoot load is not working as I expected

I am trying to get the load env variable functionality to work in ConfigModule in NestJS.
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
#Module({
imports: [
ConfigModule.forRoot({
load: [
() => ({
port: 3000,
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
},
}),
],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
When I inspect process.env.port it is undefined. I have a feeling I am not understanding what this is actually doing. I thought it would be adding the env variable port and database. I think it may have something to do with expandable env variables which I don't understand how to access. Can someone explain how to get this to work and how to access these variables. The code above was taken from the NestJS documentation located at https://docs.nestjs.com/techniques/configuration
Thanks
Okay, as usual I answered my own question 15 minutes after I posted it. My assumption that this modified process.env was incorrect. It looks like it stores these values internally to the ConfigModule and they are accessible through the ConfigService.
this.configService.get<number>('port') // returns 3000
process.env.port // returns undefined
I will leave this up here if it helps someone else. You know what they say about assuming.
Thanks

In Nest js, I added Redis as a cache manager. And can't find any added data in Redis after calling the set function. So, am I missing something?

Node version: v14.15.4
Nest-js version: 9.0.0
app.module.ts
Here is the code.
In the app module, I am registering Redis as a cache manager.
#Module({
imports: [
CacheModule.register({
isGlobal: true,
store: redisStore,
url: process.env.REDIS_URL,
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
service.ts
The cache data method is for storing data with a key. -> the problem is the set function doesn't save anything
And get Data for returning the data by key.
#Injectable()
export class SomeService {
constructor(#Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async cacheData(key: string, data): Promise<void> {
await this.cacheManager.set(key, data);
}
async getData(key: string, data): Promise<any> {
return this.cacheManager.get(key);
}
}
It doesn't throw any error in runtime.
i has met the same problem as you,the way to fix this is using the install cmd to change the version of cache-manager-redis-store to 2.0.0 like 'npm i cache-manager-redis-store#2.0.0'
when you finish this step, the use of redisStore can be found,then the database can be linked.
The default expiration time of the cache is 5 seconds.
To disable expiration of the cache, set the ttl configuration property to 0:
await this.cacheManager.set('key', 'value', { ttl: 0 });
I had the same problem.
It looks like NestJS v9 incompatible with version 5 of cache-manager. So you need to downgrade to v4 for now, until this issue is resolved: https://github.com/node-cache-manager/node-cache-manager/issues/210
Change your package.json to have this in the dependencies:
"cache-manager": "^4.0.0",`
Another commenter also suggested lowering the redis cache version.
I am not sure why your getData function returns Promise<void> have you tried returning Promise<any> or the data type you are expecting e.g. Promise<string>. You could also try adding await.
async getData(key: string, data): Promise<any> {
return await this.cacheManager.get(key);
}
Are you sure that you are connecting to redis successfully ?. Have you tried adding password and tls (true/false) configuration ?
#Module({
imports: [
CacheModule.register({
isGlobal: true,
store: redisStore,
url: process.env.REDIS_URL,
password: process.env.REDIS_PASSWORD,
tls: process.env.REDIS_TLS
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Set the redis store as redisStore.redisStore (You will get an autocomplete)
CacheModule.register({
isGlobal: true,
store: redisStore.redisStore,
url: process.env.REDIS_URL,
})

Integrate nestjs with sentry

I want to integrate sentry with nest.js + express but I just found raven version but that is deprecated.
I follow the sentry docs for integrate with express but dont know how to handle the 'All controllers should live here' part.
const express = require('express');
const app = express();
const Sentry = require('#sentry/node');
Sentry.init({ dsn: 'https://5265e36cb9104baf9b3109bb5da9423e#sentry.io/1768434' });
// The request handler must be the first middleware on the app
app.use(Sentry.Handlers.requestHandler());
**// All controllers should live here
app.get('/', function rootHandler(req, res) {
res.end('Hello world!');
});**
// The error handler must be before any other error middleware and after all controllers
app.use(Sentry.Handlers.errorHandler());
// Optional fallthrough error handler
app.use(function onError(err, req, res, next) {
// The error id is attached to `res.sentry` to be returned
// and optionally displayed to the user for support.
res.statusCode = 500;
res.end(res.sentry + "\n");
});
app.listen(3000);
I just created a Sample Project on Github to answer this question:
https://github.com/ericjeker/nestjs-sentry-example
Below is a partial copy of the README file. Let me know if you have any questions.
Create the needed elements
Create Sentry module, service, and interceptor
$ nest g module sentry
$ nest g service sentry
$ nest g interceptor sentry/sentry
SentryModule
Create the SentryModule.forRoot() method and add the Sentry.init(options) in it.
Call the SentryModule.forRoot({...}) in the AppModule and integrate with your preferred configuration (I use ConfigModule and a .env file).
Add the call to the Express requestHandler middleware in the AppModule.configure().
configure(consumer: MiddlewareConsumer): void {
consumer.apply(Sentry.Handlers.requestHandler()).forRoutes({
path: '*',
method: RequestMethod.ALL,
});
}
It is important to use that middleware otherwise the current Hub will be global and
you will run into conflicts as Sentry creates a Hub by thread and Node.js is not multi-threaded.
SentryService
We want to initialize the transaction in the constructor of the service. You can
customize your main transaction there.
Note that because I inject the Express request, the service must be request scoped. You
can read more about that here.
#Injectable({ scope: Scope.REQUEST })
export class SentryService {
constructor(#Inject(REQUEST) private request: Request) {
// ... etc ...
}
}
SentryInterceptor
The SentryInterceptor will capture the exception and finish the transaction. Please also
note that it must be request scoped as we inject the SentryService:
#Injectable({ scope: Scope.REQUEST })
export class SentryInterceptor implements NestInterceptor {
constructor(private sentryService: SentryService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// ... etc ...
}
}
As an example, I added a span. This is not necessary, but it will just make the trace nicer in the performance viewer of Sentry.
You can add more spans anywhere in your application simply by injecting the SentryService and calling startChild or by simply calling the startChild method of the current span.
For integrate sentry with nestjs we to follow this steps:
install npm i nest-raven
In main.ts
async function bootstrap() {
Sentry.init({ dsn: 'https://5265e36cb9104baf9b3109bb5da9423e#sentry.io/1768434' });
const app = await NestFactory.create(AppModule);
// middlewares
await app.listen(3000);
}
For use it to all controllers in app.module.ts
#Module({
imports: [
RavenModule,...
],
controllers: [],
providers: [{
provide: APP_INTERCEPTOR,
useValue: new RavenInterceptor({
filters: [
// Filter exceptions of type HttpException. Ignore those that
// have status code of less than 500
{ type: HttpException, filter: (exception: HttpException) => 500 > exception.getStatus() },
],
}),
}],
})
The issue was tracking here https://github.com/getsentry/sentry-javascript/issues/2269, there you can follow an example.

Best practice to use config service in NestJS Module

I want to use environment variables to configure the HttpModule per module, from the docs I can use the configuration like this:
#Module({
imports: [HttpModule.register({
timeout: 5000,
maxRedirects: 5,
})],
})
But I don't know what is the best practice to inclue a baseURL from environment vairable (or a config service), for example like this:
#Module({
imports: [HttpModule.register({
baseURL: this.config.get('API_BASE_URL'),
timeout: 5000,
maxRedirects: 5,
})],
The this.config is undefined here cause it's out of class.
What is the best practice to set baseURL from environment variables (or config service)?
Update Jan 19
HttpModule.registerAsync() was added in version 5.5.0 with this pull request.
HttpModule.registerAsync({
imports:[ConfigModule],
useFactory: async (configService: ConfigService) => ({
baseURL: configService.get('API_BASE_URL'),
timeout: 5000,
maxRedirects: 5,
}),
inject: [ConfigService]
}),
Original Post
This problem was discussed in this issue. For the nestjs modules like the TypeOrmModule or the MongooseModule the following pattern was implemented.
The useFactory method returns the configuration object.
TypeOrmModule.forRootAsync({
imports:[ConfigModule],
useFactory: async (configService: ConfigService) => ({
type: configService.getDatabase()
}),
inject: [ConfigService]
}),
Although Kamil wrote
Above convention is now applied in all nest modules and will be
treated as a best practice (+recommendation for 3rd party modules).
More in the docs
it does not seem to be implemented for the HttpModule yet, but maybe you can open an issue about it. There are also some other suggestions in the issue I mentioned above.
Also have a look at the official docs with best practices on how to implement a ConfigService.
Although the top rated answer to this question is technically correct for most implementations, users of the #nestjs/typeorm package, and the TypeOrmModule should use an implementation that looks more like the below.
// NestJS expects database types to match a type listed in TypeOrmModuleOptions
import { TypeOrmModuleOptions } from '#nestjs/typeorm/dist/interfaces/typeorm-options.interface';
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [mySettingsFactory],
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
type: configService.get<TypeOrmModuleOptions>('database.type', {
infer: true, // We also need to infer the type of the database.type variable to make userFactory happy
}),
database: configService.get<string>('database.host'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
logging: true,
}),
inject: [ConfigService],
}),
],
controllers: [],
})
export class AppRoot {
constructor(private connection: Connection) {}
}
The major thing this code is doing is retrieving the correct typings from TypeORM (see the import) and using them to hint the return value configService.get() method. If you don't use the correct TypeORM typings, Typescript would get mad.
I also encountered several issues with implementing a ConfigService as described in the NestJS documentation (no type-safety, no modularity of configuration values, ...), I wrote down our company's final NestJS configuration management strategy in great detail here: NestJS Configuration Management
The basic idea is to have a central config module that loads all configuration values from the processes' environment. However, instead of providing a single service to all modules, each module can inject a dedicated subset of the configuration values! So each module contains a class that specifies all configuration values that this module needs to be provided at runtime. This simultaneously gives the developer type-safe access to configuration values (instead of using string literals throughout the codebase)
Hope this pattern also works for your use-case :)
Great answer by #Kim Kern, which clearly goes over injection of the ConfigService into a module configuration, that might be dependent on environment variables; however, from my personal experience, your app-root module or some other module with a couple of imports might get crowded and/or hard to read as well as understand the imports, module configuration and what the module you are defining relies on. So, thanks to Jay McDoniel, who curated me on this question, you can move configuration logic into a separate file.
First Solution
Example of app.module.ts:
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { MikroOrmModule } from '#mikro-orm/nestjs';
import { AppService } from './users.service';
import { AppController } from './users.controller';
import { get_db_config } from './config/database.config';
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
expandVariables: true,
}),
MikroOrmModule.forRootAsync( get_db_config() ),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Example of config/database.config.ts:
import { MikroOrmModuleAsyncOptions } from "#mikro-orm/nestjs";
import { ConfigService } from "#nestjs/config";
export function get_db_config(): MikroOrmModuleAsyncOptions
{
return {
useFactory: (configService: ConfigService) =>
({
dbName: 'driver',
type: 'postgresql',
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
user: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
autoLoadEntities: true
}),
inject: [ConfigService]
}
}
However, NestJS Docs - Configuration Namespaces as well as NestJS Authentication and Authorization Course provide an alternative method of solving this issue.
Second Solution
Example of auth.module.ts:
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { JwtModule } from '#nestjs/jwt';
import jwtConfig from './jwt.config';
#Module({
imports: [
ConfigModule.forFeature( jwtConfig ),
JwtModule.registerAsync( jwtConfig.asProvider() ),
]
})
export class AuthModule {}
Example of jwt.config.ts:
import { registerAs } from "#nestjs/config"
export default registerAs('jwt', () => {
return {
secret: process.env.JWT_SECRET,
issuer: process.env.JWT_TOKEN_ISSUER,
accessTokenTtl: parseInt(process.env.JWT_TOKEN_TTL)
};
});

Is there a way to have a base class for Jasmine Unit Tests (Angular 4)?

I have several .spec.ts files that all need the following beforeEach:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpModule, CovalentHttpModule.forRoot({
interceptors: [{
interceptor: CustomInterceptorService, paths: ['**'],
}],
}),],
declarations: [LoginComponent],
providers: [AuthService, { provide: Router, useClass: class { navigate = jasmine.createSpy('navigate'); } }, LoggingService, CustomInterceptorService],
})
.compileComponents();
}));
Is there a way to outsource this TestBed configuration ? Right now I have to adapt every new test file with the same imports and providers.
I am looking for something like a Base Unit Test. Is it possible with Jasmine ?
This case isn't really specific to Jasmine. If a function should be reused, this can be done by means of JavaScript/TypeScript.
A helper function:
export const setupFooTestbed = async(() => {
TestBed.configureTestingModule({...})...
});
...
beforeEach(setupFooTestbed);
Or it can be a base class for TestModuleMetadata object that is accepted by TestBed.configureTestingModule:
export FooTestModuleMetadata implements TestModuleMetadata { ... }
...
beforeEach(async(() => {
TestBed.configureTestingModule(new FooTestModuleMetadata)...
}));
Or a common testing module can be supplied to initTestEnvironment method:
Initialize the environment for testing with a compiler factory, a PlatformRef, and an angular module. These are common to every test in the suite.
This may only be called once, to set up the common providers for the current test suite on the current platform. If you absolutely need to change the providers, first use resetTestEnvironment.

Categories

Resources