Search code examples
angularunit-testingjasminekarma-jasmine

Error 'Cannot read property 'random_value' of undefined' of undefined in Unit Test


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;
    }));

Solution

  • 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.

    1. Change the type of your pictureService variable
    let pictureService: jasmine.SpyObj<PictureService>
    
    1. create your SpyObj before 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({
        ...
    
    1. Before you create your component (and therefore trigger OnInit), you can setup what you want your mock functions to return when they are called.
    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