Dependency injection issue for `#ntegral/nestjs-sentry` package in nestjs app - nest

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
},
],
})

Related

Testing Angular 14 Standalone Components with Spectator

Our test runner is Jest.
Our component is marked as standalone: true,
If try to set up spectator like this:
describe('OurComponent', () => {
let spectator: Spectator<OurComponent>;
const fakeActivatedRoute: ActivatedRoute = {
snapshot: {data: {}},
} as ActivatedRoute;
const componentFactory: SpectatorFactory<OurComponent> = createComponentFactory({
component: OurComponent,
imports: [
// some imports
],
providers: [
// some providers
],
detectChanges: false,
shallow: true,
});
beforeEach(async () => {
spectator = componentFactory();
});
it('should be created', () => {
expect(spectator).toBeDefined();
});
});
Then we run into the following error:
"Error: Unexpected "OurComponent" found in the "declarations" array of the "TestBed.configureTestingModule" call, "OurComponent" is marked as standalone and can't be declared in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?"
Using the Angular-CLI in order to generate resulted in a component with a test file which is built upon ComponentFixture.
How can we make it possible to test a standalone component using Spectator?
Depends on your spectator version (mine is 10.0.0) but you can use the declareComponent property :
const componentFactory: SpectatorFactory<OurComponent> = createComponentFactory({
component: OurComponent,
declareComponent: false,
});

How to test dynamic modules in Nest.js?

My implementation is based on this article: https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370
I want to test my generic, Twilio-based SMS sender service that I share between multiple parts of my application. I want to configure it when I'm importing it from somewhere else, so I'm writing it as a dynamic module. On top of that, the options that I pass to the dynamic module are themselves constructed dynamically, they are read from my .env file. I'm using the factory pattern when I'm registering my provider:
// app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [
'.env',
],
validationSchema,
}),
SharedSmsModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService<EnvironmentVariables>) => {
return {
accountSid: configService.get('TWILIO_ACCOUNT_SID'),
authToken: configService.get('TWILIO_AUTH_TOKEN'),
smsSenderPhoneNumber: configService.get(
'TWILIO_SMS_SENDER_PHONE_NUMBER'
),
};
},
}),
],
})
export class AppModule {}
My shared-sms module calls the function provided in the registerAsync method in app.module.ts:
// shared-sms.module.ts
export interface SharedSmsModuleOptions {
accountSid: string;
authToken: string;
smsSenderPhoneNumber: string;
}
export interface SharedSmsModuleAsyncOptions extends ModuleMetadata {
imports: any[];
inject: any[];
useFactory?: (
...args: any[]
) => Promise<SharedSmsModuleOptions> | SharedSmsModuleOptions;
}
#Module({})
export class SharedSmsModule {
static registerAsync(
sharedSmsModuleAsyncOptions: SharedSmsModuleAsyncOptions
): DynamicModule {
return {
global: true,
module: SharedSmsModule,
imports: sharedSmsModuleAsyncOptions.imports,
providers: [
{
provide: 'SHARED_SMS_OPTIONS',
useFactory: sharedSmsModuleAsyncOptions.useFactory,
inject: sharedSmsModuleAsyncOptions.inject || [],
},
SharedSmsService,
],
exports: [SharedSmsService],
};
}
}
Now I have access to the options variables in my shared-sms.service:
// shared-sms.service
#Injectable()
export class SharedSmsService {
private twilioClient: Twilio;
constructor(
#Inject('SHARED_SMS_OPTIONS') private options: SharedSmsModuleOptions
) {
this.twilioClient = new Twilio(
this.options.accountSid,
this.options.authToken
);
}
async sendSms(sendSmsDto: SendSmsDto): Promise<MessageInstance> {
await validateOrReject(plainToInstance(SendSmsDto, sendSmsDto));
const smsData = {
from: this.options.smsSenderPhoneNumber,
to: sendSmsDto.to,
body: sendSmsDto.body,
};
return await this.twilioClient.messages.create(smsData);
}
}
So long everything seems to be working. But I'm having issues when I'm trying to test the service's sendSms function. I can write tests that work when I'm providing hardcoded Twilio test account values in my test file. But I don't want to commit them to the repository, so I would want to get them from my .env file. I have tried providing everything to the Test.createTestingModule function when I'm creating my moduleRef, based on what I did in the code that I already wrote, but I couldn't specify the Twilio test account values dynamically. As I don't see documentation regarding this issue, I feel like that I'm either missing a conceptual point (providing so many things in the test seems like an overkill) or there is a trivial work-around. Please help me figure out how to pass those values to my tests from my .env file

How to configure middleware in e2e test in nestjs

In real app, we write:
export class AppModule implements NestModule {
constructor() {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtExtractionMiddleware).forRoutes({
path: 'graphql',
method: RequestMethod.ALL,
});
}
}
In e2e test, I do something like this:
const module = await Test.createTestingModule({
imports: [ GraphQLModule.forRoot(e2eGqlConfig) ],
providers: [ PubUserResolver ],
}).compile();
app = await module.createNestApplication().init();
So how can I specific middleware in e2e test?
Maybe try to create a specific TestModule class only for e2e and provide it to the createTestingModule?
#Module({
imports: [ GraphQLModule.forRoot(e2eGqlConfig) ],
providers: [ PubUserResolver ],
})
export class TestModule implements NestModule {
constructor() {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtExtractionMiddleware).forRoutes({
path: 'graphql',
method: RequestMethod.ALL,
});
}
}
And then in e2e:
const module = await Test.createTestingModule({
imports: [TestModule]
}).compile();
app = await module.createNestApplication().init();
I had similar problem, I needed to attach global middlewares. There is no info on the Internet about that as well, but by chance I've found the solution. Maybe someone will be looking for it, so here it is:
To use global middleware in e2e in NestJS:
Firstly create the app, but don't init it. Only compile:
const app = Test
.createTestingModule({ imports: [AppModule] })
.compile()
.createNestApplication();
After that you can add all your global middlewares:
app.enableCors();
app.use(json());
app.use(formDataMiddleware(config));
Now init the app and that's it:
await app.init();
You'll need to put app.use(new AuthMiddleware().use); before app.init().
describe('Module E2E', () => {
const mockedTest = {
create: jest.fn().mockImplementation((t) => Promise.resolve(t)),
};
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
],
controllers: [TestController],
providers: [
TestService, // the service contains a MySQL Model
{
provide: getModelToken(Test), // Test is the name of Model
useValue: mockedTest,
},
],
}).compile();
app = moduleRef.createNestApplication();
app.use(new AuthMiddleware().use); // auth middleware
await app.init();
});
});

How override Provider in Angular 5 for only one test?

In one of my unit test files, I have to mock several times the same service with different mocks.
import { MyService } from '../services/myservice.service';
import { MockMyService1 } from '../mocks/mockmyservice1';
import { MockMyService2 } from '../mocks/mockmyservice2';
describe('MyComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MyComponent
],
providers: [
{ provide: MyService, useClass: MockMyService1 }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MapComponent);
mapComponent = fixture.componentInstance;
fixture.detectChanges();
});
describe('MyFirstTest', () => {
it('should test with my first mock', () => {
/**
* Test with my first mock
*/
});
});
describe('MySecondTest', () => {
// Here I would like to change { provide: MyService, useClass: MockMyService1 } to { provide: MyService, useClass: MockMyService2 }
it('should test with my second mock', () => {
/**
* Test with my second mock
*/
});
});
});
I see that the function overrideProvider exists, but I did not manage to use it in my test. When I use it in a "it", the provider doesn't change. I didn't manage to find an example where this function is called. Could you explain me how to use it properly? Or have you an other method to do that?
As of angular 6 I noticed that overrideProvider works with the useValue property. So in order to make it work try something like:
class MockRequestService1 {
...
}
class MockRequestService2 {
...
}
then write you TestBed like:
// example with injected service
TestBed.configureTestingModule({
// Provide the service-under-test
providers: [
SomeService, {
provide: SomeInjectedService, useValue: {}
}
]
});
And whenever you want to override the provider just use:
TestBed.overrideProvider(SomeInjectedService, {useValue: new MockRequestService1()});
// Inject both the service-to-test and its (spy) dependency
someService = TestBed.get(SomeService);
someInjectedService = TestBed.get(SomeInjectedService);
Either in a beforeEach() function or place it in an it() function.
If you need TestBed.overrideProvider() with different values for different test cases, TestBed is frozen after call of TestBed.compileComponents() as #Benjamin Caure already pointed out. I found out that it is also frozen after call of TestBed.get().
As a solution in your 'main' describe use:
let someService: SomeService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{provide: TOKEN, useValue: true}
]
});
// do NOT initialize someService with TestBed.get(someService) here
}
And in your specific test cases use
describe(`when TOKEN is true`, () => {
beforeEach(() => {
someService = TestBed.get(SomeService);
});
it(...)
});
describe(`when TOKEN is false`, () => {
beforeEach(() => {
TestBed.overrideProvider(TOKEN, {useValue: false});
someService = TestBed.get(SomeService);
});
it(...)
});
If the service is injected as public property, e.g.:
#Component(...)
class MyComponent {
constructor(public myService: MyService)
}
You can do something like:
it('...', () => {
component.myService = new MockMyService2(...); // Make sure to provide MockMyService2 dependencies in constructor, if it has any.
fixture.detectChanges();
// Your test here...
})
If injected service is stored in a private property, you can write it as (component as any).myServiceMockMyService2 = new MockMyService2(...); to bypass TS.
It's not pretty but it works.
As for TestBed.overrideProvider, I had no luck with that approach (which would be much nicer if it worked):
it('...', () =>{
TestBed.overrideProvider(MyService, { useClass: MockMyService2 });
TestBed.compileComponents();
fixture = TestBed.createComponent(ConfirmationModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
// This was still using the original service, not sure what is wrong here.
});
I was facing similar problem, but in a simpler scenario, just one test(describe(...)) with multiple specifications(it(...)).
The solution that worked for me was postponing the TestBed.compileComponents and the TestBed.createComponent(MyComponent) commands.
Now I execute those on each individual test/specification, after calling TestBed.overrideProvider(...) when needed.
describe('CategoriesListComponent', () => {
...
beforeEach(async(() => {
...//mocks
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([])],
declarations: [CategoriesListComponent],
providers: [{provide: ActivatedRoute, useValue: mockActivatedRoute}]
});
}));
...
it('should call SetCategoryFilter when reload is false', () => {
const mockActivatedRouteOverride = {...}
TestBed.overrideProvider(ActivatedRoute, {useValue: mockActivatedRouteOverride });
TestBed.compileComponents();
fixture = TestBed.createComponent(CategoriesListComponent);
fixture.detectChanges();
expect(mockCategoryService.SetCategoryFilter).toHaveBeenCalledTimes(1);
});
Just for reference, if annynone meets this issue.
I tried to use
TestBed.overrideProvider(MockedService, {useValue: { foo: () => {} } });
it was not working, still the original service was injected in test (that with providedIn: root)
In test I used alias to import OtherService:
import { OtherService } from '#core/OtherService'`
while in the service itself I had import with relative path:
import { OtherService } from '../../../OtherService'
After correcting it so both test and service itself had same imports TestBed.overrideProvider() started to take effect.
Env: Angular 7 library - not application and jest
I needed to configure MatDialogConfig for two different test scenarios.
As others pointed out, calling compileCompents will not allow you to call overrideProviders. So my solution is to call compileComponents after calling overrideProviders:
let testConfig;
beforeEach(waitForAsync((): void => {
configuredTestingModule = TestBed.configureTestingModule({
declarations: [MyComponentUnderTest],
imports: [
MatDialogModule
],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: { testConfig } }
]
});
}));
const buildComponent = (): void => {
configuredTestingModule.compileComponents(); // <-- compileComponents here
fixture = TestBed.createComponent(MyComponentUnderTest);
component = fixture.componentInstance;
fixture.detectChanges();
};
describe('with default mat dialog config', (): void => {
it('sets the message property in the component to the default', (): void => {
buildComponent(); // <-- manually call buildComponent helper before each test, giving you more control of when it is called.
expect(compnent.message).toBe(defaultMessage);
});
});
describe('with custom config', (): void => {
const customMessage = 'Some custom message';
beforeEach((): void => {
testConfig = { customMessage };
TestBed.overrideProvider(MAT_DIALOG_DATA, { useValue: testConfig }); //< -- override here, before compiling
buildComponent();
});
it('sets the message property to the customMessage value within testConfig', (): void => {
expect(component.message).toBe(customMessage);
});
});

ORIGINAL EXCEPTION: No provider for Auth! : Ionic Cloud Services

I set up Ionic Cloud Service and went through the initial process of authorizing a user.
import {Component} from '#angular/core';
import {NavController} from 'ionic-angular';
import {Auth, User, UserDetails, IDetailedError} from '#ionic/cloud-angular';
#Component({
templateUrl: 'build/pages/signup/signup.html'
})
export class SignupPage {
constructor(public auth: Auth, public user: User){
let details: UserDetails = {'email': 'hi#ionic.io', 'password': 'puppies123'};
this.auth.signup(details).then(() => {
// `this.user` is now registered
}, (err: IDetailedError<string[]>) => {
for (let e of err.details) {
if (e === 'conflict_email') {
alert('Email already exists.');
} else {
// handle other errors
}
}
});
}
}
For some reason I am getting this error:ORIGINAL EXCEPTION: No provider for Auth!
ORIGINAL STACKTRACE:
Error: DI Exception
Everything is setup to a tee like the ionic cloud docs suggest : https://docs.ionic.io/services/auth/#setup
I've looked everywhere for this answer
In the setup instructions it talks about how to add the ionic cloud NgModule to your module's imports:
https://docs.ionic.io/setup.html
import { CloudSettings, CloudModule } from '#ionic/cloud-angular';
const cloudSettings: CloudSettings = {
'core': {
'app_id': 'APP_ID'
}
};
#NgModule({
declarations: [ ... ],
imports: [
IonicModule.forRoot(MyApp),
CloudModule.forRoot(cloudSettings)
],
bootstrap: [IonicApp],
entryComponents: [ ... ],
providers: [ ... ]
})
export class AppModule {}
I had missed these steps. Making this change fixed the problem.
Try this
#Component({
templateUrl: 'build/pages/signup/signup.html',
providers: [Auth]
})
Not sure if it works because the ionic docs don't say anything about this, but it seems logical looking at your Error
Passing Auth in providers, starts to show that error in console:
Cannot read property 'config' of undefined