I'm facing the following issue. I have a service used by a controller. The service (in the snippets below QueueService) injects a provider imported from a package. I aim to reuse the QueueService across the controller methods, but I also need to dynamically specify which provider QueueService should use.
My question is, how can I achieve this behaviour?
import { PubsubService } from '#myorg/queue'
#Module({
imports: [
ConfigModule.forRoot({
SHARED_RESOURCES_PROJECT_ID: Joi.string().required()
})
})
],
controllers: [AppController],
providers: [
{
provide: 'PUBSUB',
useValue: new PubsubService()
},
{
provide: 'INTEGRATION_PUBSUB',
useValue: new PubsubService({ projectId: process.env.SHARED_RESOURCES_PROJECT_ID })
}
]
})
export class AppModule {}
#Controller()
export class AppController {
constructor(private queueService: QueueService) {}
#Post()
async create() {
...
// here I want to use queueService with `PUBSUB` injected
return this.queueService.sendMessage(...)
}
#Patch()
async update() {
...
// here I want to use queueService with `INTEGRATION_PUBSUB` injected
return this.queueService.sendMessage(...)
}
}
#Injectable()
export class QueueService {
constructor(
// how can I dynamically change `#Inject('PUBSUB')` to `#Inject('INTEGRATION_PUBSUB')`?
#Inject('PUBSUB') private readonly pubsubService: PubsubService
) {}
async sendMessage(payload): Promise<void> {
return this.pubsubService.sendMessage(payload)
}
}
dynamic inject is not possible after object(in this case controller) created . so you have two option
1- create two QueueService (one for PUBSUB and another for INTEGRATION_PUBSUB) and inject both to controller. use those in your controller functions. (i recommend this)
2- inject both PUBSUB and INTEGRATION_PUBSUB into QueueService and pass another param in sendMessage function . so check this param to choose between PUBSUB and INTEGRATION_PUBSUB
I am have an issue with this package #ntegral/nestjs-sentry in nestjs. I have a custom logger I use in my application
#Injectable()
export class CustomLogger implements LoggerService {
constructor(#InjectSentry() private readonly client: SentryService) {}
log(message: any, ...optionalParams: any[]) {
this.client.instance().captureMessage(message, ...optionalParams);
}
}
I then inject the into User Controller and in the user.controller.spec.ts
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
providers: [
CustomLogger,
UsersService,
SentryService,
],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
I get this error
FAIL src/users/users.controller.spec.ts (9.449 s)
● UsersController › should be defined
Nest can't resolve dependencies of the CustomLogger (?). Please make sure that the argument Symbol(SentryToken) at index [0] is available in the RootTestModule context.
Potential solutions:
- If Symbol(SentryToken) is a provider, is it part of the current RootTestModule?
- If Symbol(SentryToken) is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing Symbol(SentryToken) */ ]
})
I have tried adding the SentryService to the spec providers but that does not fix the error. Has anyone encountered this and how did you fix it.
I run in exactly the same issue. It seemed to be that the library uses a different token for its own Inject annotation. I was able to fix it in my tests by using the provided token for the SentryService mock.
import { SENTRY_TOKEN } from '#ntegral/nestjs-sentry';
// ...
const module: TestingModule = await Test.createTestingModule({
providers: [
// ...
{
provide: SENTRY_TOKEN,
useValue: { debug: jest.fn() }, // provide SentryService Mock here
},
],
})
I am writing an application to handle requests and return predefined responses to allow testing of external REST endpoints by software that cannot have internal tests written into it currently. Therefore, my code uses the Nest JS framework to handle the routes, and then extracts values and returns data. The data returned is stored in external files.
To handle constant changes and different team usage, the program uses a .env file to give the base (root) directory where the files to respond are located. I am trying to write a test case to ensure that the NestJS ConfigService is working properly, but also to use as a base for all my other tests.
With different routes, different data files need to be returned. My code will need to mock all these files. As this data relies on the base ConfigService having read the .env to find the base paths, my routes are based on this starting point.
During development, I have a local .env file with these values set. However, I want to test without this .env file being used, so my tests do not rely on the presence of the .env file, since the CI/CD server, build server, etc., will not have a .env file.
I am simply trying a test file then to work with the configuration, to get set data from my mock.
nestjs-config.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigModule, ConfigService } from '#nestjs/config';
describe('NestJS Configuration .env', () => {
let service: ConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
expandVariables: true,
}),
],
providers: [
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
if (key === 'FILES') {
return './fakedata/';
} else if (key === 'PORT') {
return '9999';
}
return null;
}),
},
},
],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it.each([
['FILES=', 'FILES', './', './fakedata/'],
['PORT=', 'PORT', '2000', '9999'],
['default value when key is not found', 'NOTFOUND', './', './'],
])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
const Result: string = service.get<string>(Key, Default);
expect(Key).toBeDefined();
expect(Result).toBe(Expected);
});
});
The problem in this test is the default values are always returned, meaning the .env file was not read, but the provider had the code to handle this.
Ideally, I would like to create a fake class for testing so I could use it in all my test files. However, when trying to create the fake class, I get an error about other methods missing, that are unrelated to this class.
export class ConfigServiceFake {
get(key: string) {
switch (key) {
case 'FILES':
return './fakedata/';
case 'PORT':
return '9999';
}
}
}
This does not seem to execute, and it appears to still go through the original service.
I was able to adjust this and not need external references, including importing the configuration module, making the mock simpler, and not needing the full definitions.
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigService } from '#nestjs/config';
describe('NestJS Configuration .env', () => {
let service: ConfigService;
afterEach(() => {
jest.clearAllMocks();
});
beforeEach(async () => {
const FakeConfigService = {
provide: ConfigService,
useValue: {
get: jest.fn((Key: string, DefaultValue: string) => {
switch (Key) {
case 'FILES':
return './fakedata/';
break;
case 'PORT':
return '9999';
break;
default:
return DefaultValue;
}
}),
},
};
const module: TestingModule = await Test.createTestingModule({
providers: [FakeConfigService],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it.each([
['FILES=', 'FILES', './', './fakedata/'],
['PORT=', 'PORT', '2000', '9999'],
['default value when key is not found', 'NOTFOUND', './', './'],
])('should get from the .env file, %s', (Text: string, Key: string, Default: string, Expected: string) => {
const Result: string = service.get<string>(Key, Default);
expect(Key).toBeDefined();
expect(Result).toBe(Expected);
});
});
i'm working on an Angular project. I've firstly add translaService in the project and everything were good. The language were shared by all components correctly.
When i've enable the lazy loading, i have divided my project by modules (this was expected) and i've notice that the translated language were not shared anymore accross modules. For translation, i use ngx-translate.
in app.module i have this:
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (httpTranslateLoader),
deps: [HttpClient]
},
isolate:false
})
export function httpTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
in my shared module i have this:
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http);
}
#NgModule({
declarations: [],
imports: [
CommonModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
},
isolate: false,
extend:true
})
]
,
exports:[TranslateModule]
})
export class SharedTranslateModule {
constructor( translate: TranslateService) {
translate.addLangs(['en', 'fr']);
translate.setDefaultLang('en');
translate.use('en');
}
}
in my about module i have this:
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: (aboutHttpTranslateLoader),
deps: [HttpClient]
},
isolate: false
})
export function aboutHttpTranslateLoader(http : HttpClient){
return new TranslateHttpLoader(http, './assets/i18n/about/', '.json')
}
my project is divide like this
|app.component.html
<app-header> </app-header>
<router-outlet> <router-outlet>
<app-footer> </app-footer>
app-header is the component where i switch languages by using translate.use(selected-language)
I'm out of ideas. i have try many things but they doesn't solve the problem.
Can someone help me?
Thank you!
I created an app in angular 5. Now I want to add some tests.
At the moment almost all the specs are failing even though I didnt even add anything.
For my first component it says:
Can't bind to 'formGroup' since it isn't a known property of 'form'.
How do i inject the necessary dependencys in the component?
Right now I have this:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ],
providers: [{
provide: Router,
useClass: class { navigate = jasmine.createSpy("navigate"); }
}, AuthentificationService, NotificationService, FormBuilder]
})
.compileComponents();
}));
This is the constructor of the component:
constructor(private router: Router, private auth: AuthentificationService, private ns: NotificationService, private fb: FormBuilder) {}
At the moment almost all the specs are failing even though I didnt even add anything.
Seems like you are using something like this or this.
For my first component it says: Can't bind to 'formGroup' since it isn't a known property of 'form'.
Something like this should take care of this particular error, note the imports-statement after declarations:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ],
imports: [ReactiveFormsModule],
providers: [{
provide: Router,
useClass: class { navigate = jasmine.createSpy("navigate"); }
}, AuthentificationService, NotificationService, FormBuilder]
})
.compileComponents();
}));
You'll of course have to add it to the imports at the file level as well but it seems you got those details already.