Testing Class with #Injectable Scope / #Inject(REQUEST) NestJS - nestjs

I have set up a MongooseConfigService to allow us to dynamically switch out the connection string for certain requests and am trying to get the tests set up correctly.
#Injectable({scope: Scope.REQUEST})
export class MongooseConfigService implements MongooseOptionsFactory {
constructor(
#Inject(REQUEST) private readonly request: Request) {
}
I am however having trouble providing request to the test context.
let service: Promise<MongooseConfigService>;
beforeEach(async () => {
const req = new JestRequest();
const module: TestingModule = await Test.createTestingModule({
providers: [
MongooseConfigService,
{
provide: getModelToken(REQUEST),
inject: [REQUEST],
useFactory: () => ({
request: req,
}),
},
],
}).compile();
service = module.resolve<MongooseConfigService>(MongooseConfigService);
});
This is as far as I have gotten, have tried it without the inject and with/without useValue, however this.request remains undefined when trying to run the tests.
TIA

maybe try to instantiate the testing module with only import: [appModule] and then try to get the service. also, try to use overrideprovider and usevalue like this:
let service: Promise<MongooseConfigService>;
beforeEach(async () => {
const req = new JestRequest();
const module: TestingModule = await Test.createTestingModule({
imports: [appModule]
}).overrideProvider(REQUEST)
.useValue(req)
.compile();
service = module.resolve<MongooseConfigService>(MongooseConfigService);
});

Related

NestJS Jest error: TypeError: Cannot read properties of undefined (reading '[any variable from required config]')

While trying to cover out project in unit tests using nest's jest I've bumped into a problem of a testing module not being able to pull variables from config.
Basically, I have an EmailService, I want to test it, I use it as a Provider in my testing module. Naturally, as EmailService takes ConfigService in its constructor to pull some variables from config (that initially come from env) I put ConfigService into the providers array as well... well, then upon initialization testing module drops
NestJS Jest error: TypeError: Cannot read properties of undefined (reading 'region')
note: region variable is taken from env in a registered config module
code example of my test that throws
describe('EmailService', () => {
let emailService: EmailService;
let configService: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [EmailService, ConfigService],
}).compile();
emailService = module.get<EmailService>(EmailService);
configService = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(emailService).toBeDefined();
});
});
I have came to the conclusion that it throws an error specifically because EmailService takes ConfigService in it's constructor in this way:
export class EmailService {
private readonly config: IAwsConfig;
private readonly region: IRegion;
constructor(private readonly configService: ConfigService) {
this.config = this.configService.get('aws');
this.region = this.config.region;
}
aditional info: both EmailService and ConfigService work just fine during a normal runtime, it only fails during jest testing
seems like this.configService.get method returns 'undefined' during a test run and i'm, not sure why or how to fix it. Any ideas?
In case you don't want to import the entire ConfigService but just the config values themselves, then you use them in the test as follows :)
// my-config.ts
import { registerAs } from '#nestjs/config';
export default registerAs('myConfig', () => ({ propA: 'aa', propB: 123 }));
import { Inject } from '#nestjs/common';
import { ConfigType } from '#nestjs/config';
import myConfig from './my-config.ts';
export class EmailService {
private propA: string;
private propB: number;
constructor(
#Inject(myConfig.KEY) config: ConfigType<typeof myConfig>
) {
this.propA = config.propA;
this.propB = config.propB;
}
}
import { ConfigModule, registerAs } from '#nestjs/config';
import { Test, TestingModule } from '#nestjs/testing';
describe('Test', () => {
const configValues = { propA: 'aa', proprB: 123 };
const config = registerAs('testConfig', () => configValues);
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forFeature(config)],
providers: [EmailService],
}).compile();
});
});
Was not able to find an answer for 2 hours straight, but then, 10 minutes after asking a question, there you go, an answer.
Seems like ConfigService doesn't provide configs during jest testing so you have to provide it in the testing module with replaced get method, something like such:
providers: [
EmailService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
return hardcodedConfigFromWithinTheTestFile;
}),
},
},
],

Get provider injected with key in a NetJS Test

I have a provider which I inject using a key:
export const myProvider = {
provide: SOME_PROVIDER,
useFactory: () => {
return SomeWrappedInstance;
},
};
In my tests I want to access this provider which I inject with #Inject(SOME_PROVIDER), how can I do this?
const moduleRef = await Test.createTestingModule({
imports: [],
providers: [myProvider]
}).compile();
it's easy as doing:
const myProviderValue = moduleRef.get(SOME_PROVIDER)
// ^? SomeWrappedInstance

How would I mock the DataSource from TypeORM in NestJS Jest tests?

I am trying to write tests for a small project in NestJS. Here is the relevant code for context:
dummy.controller.ts
#Controller(UrlConstants.BASE_URL + 'dummy')
export class DummyContoller {
constructor(
private readonly sessionService: SessionService,
) { }
#Get('validateSession')
async checkValidateSession(#Query('sessionId') sessionId: string) {
const session = await this.sessionService.validateSession(sessionId);
console.log(session);
return { message: "OK" };
}
}
session.service.ts
#Injectable()
export class SessionService {
constructor(
private readonly sessionRepo: SessionRepository,
private readonly accountRepo: AccountRepository
) { }
#WithErrorBoundary(AuthCodes.UNKNOWN_LOGIN_ERROR)
async validateSession(sessionId: string) {
const session = await this.sessionRepo.findOneBy({ sessionId });
if (!session || this.isSessionExpired(session)) {
session && await this.sessionRepo.remove(session);
throw new HttpException({
code: AuthCodes.SESSION_TIMEOUT,
message: AuthMessages.SESSION_TIMEOUT
}, HttpStatus.UNAUTHORIZED)
}
return session;
}
...
}
session.repository.ts (Any repository)
#Injectable()
export class SessionRepository extends Repository<Session> {
constructor(private dataSource: DataSource) {
super(Session, dataSource.createEntityManager())
}
...
}
This is how I wrote my test (this is my first time writing a test using Jest and I am not really experienced in writing tests in general):
describe('DummyController', () => {
let dummyContoller: DummyContoller;
let sessionService: SessionService;
let sessionRepo: SessionRepository;
let accountRepo: AccountRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [DummyContoller],
providers: [SessionService, SessionRepository, AccountRepository]
}).compile();
dummyContoller = module.get<DummyContoller>(DummyContoller);
sessionService = module.get<SessionService>(SessionService);
sessionRepo = module.get<SessionRepository>(SessionRepository);
accountRepo = module.get<AccountRepository>(AccountRepository);
})
describe('checkValidateSession', () => {
it('should return valid session', async () => {
const sessionId = "sessionId1";
const session = new Session();
jest.spyOn(sessionService, 'validateSession').mockImplementation(async (sessionId) => session);
expect(await dummyContoller.checkValidateSession(sessionId)).toBe(session);
})
})
})
Upon running the test, I encounter:
Nest can't resolve dependencies of the SessionRepository (?). Please make sure that the argument DataSource at index [0] is available in the RootTestModule context.
Potential solutions:
- If DataSource is a provider, is it part of the current RootTestModule?
- If DataSource is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing DataSource */ ]
})
I looked this problem and I came across a number of solutions but most of them had #InjectRepository() instead of creating a separate Repository class where they would provide getRepositoryToken() and then use a mock factory [Link]. I couldn't find a way to make this work.
Another solution suggested using an in-memory database solution [Link]. But this felt more like a hack rather than a solution.
How can I test the above setup?
Based on this comment, I was able to get this working by using the following in the providers in the test:
providers: [
SessionService,
{ provide: SessionRepository, useClass: SessionMockRepository },
]
SessionMockRepository contains a mocked version of all additional functions in that particular repository:
export class SessionMockRepository extends Repository<Session> {
someFunction = async () => jest.fn();
}
Currently, this works for me so I am accepting this. I am still open to more answers if there is a better way to do this.

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)

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

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.

Resources