NestJS/TypeORM unit testing: Can't resolve dependencies of JwtService - jestjs

I'm trying to unit test this controller and mock away the services/repositories that it needs.
#Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly usersService: UsersService,
) {}
#Post('register')
public async registerAsync(#Body() createUserModel: CreateUserModel) {
const result = await this.authenticationService.registerUserAsync(createUserModel);
// more code here
}
#Post('login')
public async loginAsync(#Body() login: LoginModel): Promise<{ accessToken: string }> {
const user = await this.usersService.getUserByUsernameAsync(login.username);
// more code here
}
}
Here is my unit test file:
describe('AuthController', () => {
let authController: AuthController;
let authService: AuthService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
authController = moduleRef.get<AuthenticationController>(AuthenticationController);
authService = moduleRef.get<AuthenticationService>(AuthenticationService);
});
describe('registerAsync', () => {
it('Returns registration status when user registration succeeds', async () => {
let createUserModel: CreateUserModel = {...}
let registrationStatus: RegistrationStatus = {
success: true,
message: 'User registered successfully',
};
jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
Promise.resolve(registrationStatus),
);
expect(await authController.registerAsync(createUserModel)).toEqual(registrationStatus);
});
});
});
But when running this, I get the following error(s):
● AuthController › registerAsync › Returns registration status when user registration succeeds
Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the JwtModule context.
Potential solutions:
- If JWT_MODULE_OPTIONS is a provider, is it part of the current JwtModule?
- If JWT_MODULE_OPTIONS is exported from a separate #Module, is that module imported within JwtModule?
#Module({
imports: [ /* the Module containing JWT_MODULE_OPTIONS */ ]
})
at Injector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:191:19)
at Injector.resolveComponentInstance (../node_modules/#nestjs/core/injector/injector.js:147:33)
at resolveParam (../node_modules/#nestjs/core/injector/injector.js:101:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:116:27)
at Injector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:80:9)
at Injector.loadProvider (../node_modules/#nestjs/core/injector/injector.js:37:9)
at Injector.lookupComponentInImports (../node_modules/#nestjs/core/injector/injector.js:223:17)
at Injector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:189:33)
● AuthController › registerAsync › Returns registration status when user registration succeeds
Cannot spyOn on a primitive value; undefined given
48 | };
49 |
> 50 | jest.spyOn(authService, 'registerUserAsync').mockImplementation(() =>
| ^
51 | Promise.resolve(registrationStatus),
52 | );
53 |
at ModuleMockerClass.spyOn (../node_modules/jest-mock/build/index.js:780:13)
at Object.<anonymous> (Authentication/authentication.controller.spec.ts:50:18)
I'm not quite sure how to proceed so I'd like some help.

There's a few things I'm noticing here:
if you're testing the controller, you shouldn't need to mock more than one level deep pf services
you should almost never have a use case where you need an imports array in a unit test.
What you can do for your test case instead is something similar to the following:
beforeEach(async () => {
const modRef = await Test.createTestingModule({
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(),
}
},
{
provide: UserService,
useValue: {
getUserByUsernameAsync: jest.fn(),
}
}
]
}).compile();
});
Now you can get the auth service and user service using modRef.get() and save them to a variable to add mocks to them later. You can check out this testing repository which has a lot of other examples.

Since you are registering AuthService in the dependency injection container and just spying on registerUserAsync, it requires JWTService to be registered as well.
You need to register dependencies that are injected in AuthService:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
AuthService,
UsersService,
JWTService, // <--here
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();
or register a fully mocked AuthService that doesn't need any other dependency:
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [JwtModule],
controllers: [AuthController],
providers: [
{
provide: AuthService,
useValue: {
registerUserAsync: jest.fn(), // <--here
}
},
{
provide: getRepositoryToken(User),
useClass: Repository,
},
],
}).compile();

If you're building out a full integration test suite for NestJS then it will be easy to hit this error if you import a module that imports the AuthService. That will inevitably require the JwtService which will error out with: Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0] is available in the RootTestModule context.
Here's how I resolved this. I added:
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '1d' }
})
}),
To my imports: [] function inside my await Test.createTestingModule({ call
The final important thing to do was to not import JwtService directly. Instead, initialize JwtModule with the above code which by extension itself should internally initialize JwtService correctly.

Related

Nest can't resolve dependencies of the UsersService (?). Please make sure that the argument UserRepositor

Nest can't resolve dependencies of the UsersService (?). Please make sure that the argument UserRepository at index [0] is available in the RootTestModule context. Potential solutions: - If UserRepository is a provider, is it part of the current
I'm currently workin in v9 and I have this error, I don't understand why.
Here is my repo: https://github.com/ociel-gonzalez-solis/cv-nestjs-test-portafolio
You never provide a custom provider to mock the UserRepository. Something like
import { Test, TestingModule } from '#nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: {
create: jest.fn(),
save: jest.fn(),
find: jest.fn(),
}
},
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
You can then tailor the jest.fn() methods to your liking as your test needs. You can find a whole repository of test examples here

Nest JS Circular Dependency Issue - Mongoose Module

I am trying to inject dependencies in mongoose module for root async. I want to kind of simulate a cascade delete using mongoose hooks.
I have this module, which imports mongoose module, and it imports other modules.
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
imports: [NotificationModule, UserModule, MuseumModule, ImageModule, CommentModule],
name: Shirt.name,
useFactory: (
NotificationService: NotificationService,
UserService: UserService,
MuseumService: MuseumService,
ImageService: ImageService,
ConfigService: ConfigService,
CommentService: CommentService,
) => {
const schema = ShirtSchema;
schema.post('findOneAndDelete', async function (document: ShirtDocument) {
/* Delete notifications */
if (document) {
await NotificationService.deleteManyFromArray(document._id);
/* Remove shirt from museum */
const shirtUser = await UserService.getById(document.shirtUser.userId);
await MuseumService.removeShirtByMuseumId(shirtUser.museums[0], document._id);
/* Delete images from bucket */
if (document.images && document.images.length > 0) {
document.images.forEach(async (image) => {
if (image.thumbnail) {
await ImageService.deleteImageFromBucketS3({
bucket: ConfigService.get('AWS_THUMBNAIL_BUCKET'),
key: getImageUUID(image.thumbnail),
});
}
await ImageService.deleteImageFromBucketS3({
bucket: ConfigService.get('AWS_BUCKET'),
key: getImageUUID(image.cloudImage),
});
});
}
/* Remove shirt comments */
if (document.comments && document.comments.length > 0) {
await CommentService.deleteManyComments(document.comments.map((c) => c._id));
}
}
});
return schema;
},
inject: [NotificationService, UserService, MuseumService, ImageService, ConfigService, CommentService],
},
]),
UserModule,
TeamModule,
BrandModule,
CountryModule,
MuseumModule,
ImageModule,
],
controllers: [ShirtController],
providers: [ShirtService, ShirtRepository],
exports: [ShirtService],
})
export class ShirtModule {}
I also need to do the same in another module, but when I import the
ShirtModule
the compilation fails with the following error:
Error: Nest cannot create the module instance. Often, this is because
of a circular dependency between modules. Use forwardRef() to avoid
it. Scope [AppModule -> UserModule -> MongooseModule -> MuseumModule
-> MongooseModule -> ShirtModule -> MongooseModule]
#Module({
imports: [
MongooseModule.forFeatureAsync([
{
name: 'Museum',
imports: [ShirtModule],
useFactory: () => {
const schema = MuseumSchema;
schema.post('findOneAndDelete', async function (document: MuseumDocument) {});
return schema;
},
},
]),
],
controllers: [MuseumController],
providers: [MuseumService, MuseumRepository],
exports: [MuseumService],
})
export class MuseumModule {}
I tried using
forwardRef(() => )
In both modules, but still the same. I can not understand where is the circular dependency and how to solve it.
Please could you help me?. Also, is this is a good approach to use mongoose hooks using nest?
Thanks
Try to use forwardRef(() => MongooseModule.forFeatureAsync(xxxx)). This is work in my case.

Nest JS: TypeError: JwtStrategy requires a secret or key? when using with injection token

I want to create an auth module as a separate node package so that we can reuse it for multiple projects.
AS part of it we want to supply the necessary config from the main app via AuthLibModule. register() which supplies this param as config.
and the passed config will be held using an injection token to use it via dependency injection.
HOW DO WE ACHIEVE IT?
AuthLib.module.ts
#Module({})
export class AuthLibModule {
static register(config: AuthLibConfig): DynamicModule {
const authConfigToken = {
provide: AuthLibConstants.JWT_CONFIG_TOKEN,
useValue: config.jwtConfig
};
return {
module: AuthLibModule,
imports: [
ConfigModule.forRoot(
{
isGlobal: true
}
),
MongooseModule.forRoot(config.mongoDbUrl ? config.mongoDbUrl : process.env.MONGO_URI),
AuthModule
],
controllers: [],
exports: [authConfigToken],
providers: [authConfigToken]
}
}
}
APP.MODULE.TS
#Module({
imports: [
AuthLib.register({
authTokenExpiration: 60S,
authTokenSecret: XYZZZZZ,
refreshTokenSecret: YYYYYYYYYYYYYYYYY,
refreshTokenExpiration: 90S
})
],
controllers: [],
providers: [],
})
export class AppModule { }
jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, GuardNames.jwtAuth) {
constructor(
#Inject(AuthLibConstants.JWT_CONFIG) jwtConfig: JWTConfig
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
// jwtConfig.authTokenSecret -> getting as undefined.
secretOrKey: jwtConfig.authTokenSecret
});
}
async validate(payload: TokenParsePayload): Promise<TokenParsePayload> {
return payload;
}
}
Error:
[Nest] 21492 - 28/02/2022, 5:30:03 pm ERROR [ExceptionHandler] JwtStrategy requires a secret or key
TypeError: JwtStrategy requires a secret or key
at new JwtStrategy (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\passport-jwt\lib\strategy.js:45:15)
at new MixinStrategy (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\passport\dist\passport\passport.strategy.js:32:13)
at new JwtStrategy (D:\Freelancing\code-mentor\infra-services\auth-lib\src\auth\strategies\Jwt-auth.strategy.ts:17:9)
at Injector.instantiateClass (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:301:19)
at callback (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:48:41)
at Injector.resolveConstructorParams (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:124:24)
at Injector.loadInstance (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:52:9)
at Injector.loadProvider (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\injector.js:74:9)
at async Promise.all (index 6)
at InstanceLoader.createInstancesOfProviders (D:\Freelancing\code-mentor\infra-services\auth-lib\node_modules\#nestjs\core\injector\instance-loader.js:44:9)

Nest can't resolve dependencies: NettJS Unit testing with TypeORM

i have following error when try to Unit testing with NestJS.
Nest can't resolve dependencies of the UsersService (UsersRepository, ?, UserRoleRepository, RoleRepository, JWTService). Please make sure that the argument CredentialsRepository at index [1] is available in the RootTestModule context.
test code
describe("UsersService", () => {
let service: UsersService;
let repositoryMock: MockType<Repository<Users>>;
let model: typeof Users;
let repo: Repository<Users>;
let userController: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: "mysql",
host: config.db.host,
port: config.db.port,
username: config.db.username,
password: config.db.password,
database: config.db.database,
autoLoadEntities: true,
synchronize: true,
}),
CredentialsModule,
],
providers: [
UsersService,
CredentialsService,
AuthService,
JWTService,
{
provide: getRepositoryToken(Users),
// useValue: {
// find: jest.fn(() => [Alluser]),
// },
useFactory: repositoryMockFactory,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repositoryMock = module.get(getRepositoryToken(Users));
});
it("User details get by Id", async () => {
repositoryMock.findOne.mockReturnValue(testUser);
expect(service.findUser(testUser.id)).toEqual(testUser);
expect(repositoryMock.findOne).toHaveBeenCalledWith(testUser.id);
// expect(await service.findOne(1)).toBeCalledWith(testUser);
});
// it("All Users details ", async () => {
// expect(await service.find()).toEqual([Alluser]);
// });
// it('Create new User ', async () => {
// expect(await service.createNewUser(createUser)).toEqual(testUser);
// });
});
As per the code, it seems that you are trying to test "UserService", But in the test module, you also have "CredentialsService" which is dependent on the "CredentialsRepository", and is not mocked.
providers: [
UsersService,
CredentialsService,
AuthService,
JWTService,
{
provide: getRepositoryToken(Users),
// useValue: {
// find: jest.fn(() => [Alluser]),
// },
useFactory: repositoryMockFactory,
},
],
The basic idea of unit testing is to test one block of the code, and mock the other dependencies of the code, which in this case are "CredentialsService, AuthService, and JWTService".
If the dependencies of the code will not be mocked then the real dependent code will be executed, which will call the further dependencies of the dependent code.
So to avoid those situations, you should mock "CredentialsService, AuthService, and JWTService" services as well, the way you have done for the user service, and then the reported error will be gone.
Firstly declare the variables
let credentialService: CredentialsService;
let authService: AuthService;
let jwtService: JWTService;
And then in the beforeEach block initialize those dependent services
service = module.get<UsersService>(UsersService)
credentialService = module.get<CredentialsService>(CredentialsService)
authService = module.get<AuthService>(AuthService)
jwtService = module.get<JWTService>(JWTService)

Error Cannot find module , testing Nestjs Services

I'm having a hard time with testing services on Nestjs, i believe is something related to my lack of knowledge on how the dependency injection works for tests, weird thing is only getting errors on the test. I have 3 modules Teste, Teste2, Teste3, Teste2 imports Teste3 service, and Teste imports Teste2 service. I tried exporting Teste2 and Teste3, and importing their modules, works fine when i run npm start. Doesnt work on the test thought...
Teste
#Module({
imports: [],
providers: [ TesteService,Teste2Service],
exports: [TesteService],
controllers: [TesteController]
})
export class TesteModule {}
#Injectable()
export class TesteService {
constructor(private teste2Service: Teste2Service){}
teste(){
return this.teste2Service.hello();
}
}
Teste2
#Module({
imports: [Teste3Module],
providers: [Teste2Service],
exports: [Teste2Service]
})
export class Teste2Module {}
#Injectable()
export class Teste2Service {
constructor(private teste3Service: Teste3Service){}
hello(){
return this.teste3Service.hello();
}
}
Teste3
#Module({
providers: [Teste3Service],
exports: [Teste3Service]
})
export class Teste3Module {}
#Injectable()
export class Teste3Service {
hello(){
return 'Hello World';
}
}
the actual test
describe('TesteService', () => {
let service: TesteService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports:[Teste2Module],
providers: [TesteService],
}).compile();
service = module.get<TesteService>(TesteService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
The error
src/teste/teste.service.spec.ts
Cannot find module 'src/teste2/teste2.service' from 'teste.service.ts'
E2E cannot find absolute path.
Change to relative path: ../src/teste2

Resources