I've started programming unit test, I'm a bit newbie yet, and I'm getting the following error when launching the next code. I have reinstalled '@azure/msal-angular'; just in case it was that, but not.
Can you give me a hand?
ERROR
TypeError: Cannot read property 'getUHToken' of undefined
at getTokenAndPicture (src/app/shared/sidenav/sidenav.component.ts:31:17)
at SidenavComponent.call [as ngOnInit] (src/app/shared/sidenav/sidenav.component.ts:56:3)
at callHook (node_modules/@angular/core/fesm2020/core.mjs:2498:22)
at callHooks (node_modules/@angular/core/fesm2020/core.mjs:2467:17)
at executeInitAndCheckHooks (node_modules/@angular/core/fesm2020/core.mjs:2418:9)
at refreshView (node_modules/@angular/core/fesm2020/core.mjs:11992:21)
at renderComponentOrTemplate (node_modules/@angular/core/fesm2020/core.mjs:12094:9)
at tickRootContext (node_modules/@angular/core/fesm2020/core.mjs:13215:13)
at detectChangesInRootView (node_modules/@angular/core/fesm2020/core.mjs:13241:5)
at RootViewRef.detectChanges (node_modules/@angular/core/fesm2020/core.mjs:13757:9)
CODE
TS
public constructor(private router: Router, private userService: UserService, private rebarAuthService: RebarAuthService, private auth: MsalService, private picture: PictureService) {
this.user = this.userService;
}
public ngOnInit() {
let retries = 0;
const retryLimit = 3;
const getTokenAndPicture = () => {
this.picture.getUHToken().subscribe({
next: result => {
sessionStorage.setItem("token", result.accessToken);
this.picture.getPicture(sessionStorage.getItem("token"), this.userService.enterpriseId).subscribe(
response => {
if (response) {
this.profile = 'data:image/jpg;base64,' + response;
this.isImgProfileLoaded = true;
}
},
error => {
if (error.status === 401) {
if (retries < retryLimit) {
retries++;
getTokenAndPicture();
} else {
console.log("Maximum retry limit reached.");
}
}
}
);
}
});
};
getTokenAndPicture();
SPEC
describe('SidenavComponent', () => {
let component: SidenavComponent;
let fixture: ComponentFixture<SidenavComponent>;
let service: MsalService;
let publicClientApplication: PublicClientApplication;
let pictureService: PictureService;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
RouterTestingModule,
HttpClientTestingModule
],
declarations: [SidenavComponent],
providers: [
{ provide: UserService, useValue: mockUserService },
{ provide: MsalService, useValue: service },
{ provide: MSAL_INSTANCE, useValue: publicClientApplication },
{ provide: PictureService, useValue: pictureService },
AppConfigService,
{ provide: APP_CONFIGURATION, useValue: mockConfiguration },
{ provide: RebarAuthService, useValue: new MockRebarAuthService() }
]
}).compileComponents();
service = TestBed.inject(MsalService);
publicClientApplication = TestBed.inject(MSAL_INSTANCE) as unknown as PublicClientApplication;
}));
You are declaring a pictureService
variable here:
let pictureService: PictureService;
Then providing PictureService
with that empty (undefined) variable as you are not assigning any value to it.
{ provide: PictureService, useValue: pictureService }
Therefore, in your test bed, when the component you're testing tries to resolve the PictureService
dependency injection, it will inject your undefinied variable pictureService
. You then try to call a function from that undefinied object which obviously doesn't exist.
You should try to mock your PictureService before injecting it in your test bed. Jasmine's spyObj is useful for this.
As a side note, I see that your component is nesting observable subscriptions, which is a big anti-pattern in rxjs. You can see that the code of your component is pretty heavily nested and hard to read. I recommend looking into rxjs's pipe with operators such as switchmap or mergeMap.
Edit: Here is a quick exemple of how you could mock your PictureService
.
pictureService
variablelet pictureService: jasmine.SpyObj<PictureService>
TestBed.configureTestingModule
. This creates a SpyObj called 'PictureService' with one method called getUHToken
and one method called getPicture
beforeEach(waitForAsync(() => {
pictureService = jasmine.createSpyObj('PictureService', ['getUHToken', 'getPicture']);
TestBed.configureTestingModule({
...
pictureService.getUHToken.and.returnValue(/*mockData*/);
// This will return what you put in place of mockData
// when the getUHToken function is called.
// Since your function returns an observable, you probably want
// to use rxjs's of. ex: of(your-mock-data)
pictureService.getPicture.withArgs(/*arg1*/, /*arg2*/).and.returnValue(/*mockData*/);
// This will return what you put in place of mockData when
// the getPicture function is called with arg1 and arg2