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
Related
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.
I have created a Nestjs server and loading configs using .env file
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [`../.env.${process.env.NODE_ENV}`, '../.env'],
}),
I have e2e test cases and need to test a condition on different values for same key in ConfigService
Is there any options to change value of a key?
Credits: Micael Levi
import { createMock, DeepMocked } from '#golevelup/ts-jest';
let mockConfigService: DeepMocked<ConfigService>
let app: INestApplication;
// Before Each
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useValue: createMock<ConfigService>(),
},
],
}).compile();
app = moduleRef.createNestApplication();
// Other configs
await app.init();
mockConfigService = moduleRef.get(ConfigService)
// it(...)
jest.spyOn(mockConfigService, 'get').mockImplementation((key: string) => {
if (key === 'KEY_To_BE_MOCKED') {
return 'true';
} else {
return process.env[key];
}
});
if mocking is a headache you can always go for the "hacky" solution and manually modify the internals of the service object.
example:
config.get('someKey'); // 'original'
config.internalConfig['someKey'] = 'mockValue'; // the hacky act
config.get('someKey'); // 'mockValue'
then you can use the same object and modify it with beforeEach and afterEach or however you see fit in your tests
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)
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);
});
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.