Getting Error Cannot read properties of undefined (reading 'subscribe') in Unit testing

105 views Asked by At

I'm trying to write a Unit test for my component in angular but when it calls an Api it getting undefine from reponse and i don't know how to handle this because it's my first time writing a unit test in angular please Help !!

This is my code for the Unit Test of the component UserProfile

import { ComponentFixture, TestBed,async } from '@angular/core/testing';
import { ApiService } from 'src/app/services/api.service';
import { UserProfileComponent } from './user-profile.component';

describe('UserProfileComponent', () => {
  let component: UserProfileComponent;
  let fixture: ComponentFixture<UserProfileComponent>;
  let apiService: jasmine.SpyObj<ApiService>;

  beforeEach(() => {
    apiService = jasmine.createSpyObj('ApiService', ['getUser', 'getRepo']);

    TestBed.configureTestingModule({
      declarations: [UserProfileComponent],
      providers: [{ provide: ApiService, useValue: apiService }],
    });

    fixture = TestBed.createComponent(UserProfileComponent);
    component = fixture.componentInstance;

    // Initialize test data
    component.userDetail = { public_repos: 20 };
    component.userName = 'testuser';

    fixture.detectChanges();
  });
  
  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

This is my UserProfile Component Code

import { Component, Input, OnInit } from '@angular/core';
import { ApiService } from 'src/app/services/api.service';

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrls: ['./user-profile.component.scss'],
})
export class UserProfileComponent implements OnInit {
  @Input() userDetail: any;
  @Input() userName: any;
  perPage: number;
  pageNo: number;
  isLoading: boolean;
  repoList: any;
  noOfPages: any[];
  options: { value: number; label: number }[] = [];

  constructor(private apiService: ApiService) {
    this.perPage = 10;
    this.pageNo = 1;
    this.isLoading = false;
    this.noOfPages = new Array(10);
    for (let i = 10; i <= 100; i++) {
      const tempObj = {
        value: i,
        label: i,
      };
      this.options.push(tempObj);
    }
  }

  getUserRepos() {
    this.isLoading = true;
    this.apiService.getRepo(this.userName, this.perPage, this.pageNo).subscribe(
      (data) => {
        this.repoList = data;
        setTimeout(() => {
          this.isLoading = false;
        }, 200);
      },
      (error) => {
        error.log(error);
        setTimeout(() => {
          this.isLoading = false;
        }, 200);
      }
    );
  }
  changePageNo(pageNo: number) {
    this.pageNo =
      pageNo <= 0
        ? 1
        : pageNo > this.noOfPages.length
        ? this.noOfPages.length
        : pageNo;
    this.getUserRepos();
  }
  onPerPageNoChange() {
    this.noOfPages = new Array(
      Math.floor(this.userDetail.public_repos / this.perPage) + 1
    );
    this.pageNo = 1;
    this.getUserRepos();
  }
  ngOnInit() {
    this.noOfPages = new Array(
      Math.floor(this.userDetail.public_repos / this.perPage) + 1
    );
    console.log(this.noOfPages)
    this.getUserRepos();
  }
}

code for getRepo method in Service

getRepo(githubUsername : string,perPage : number,pageNo : number){
    return this.httpClient.get(`https://api.github.com/users/${githubUsername}/repos?per_page=${perPage}&page=${pageNo}`)
  }
1

There are 1 answers

0
Philip On

Reason for this is that you used jasmine.createSpyObj to create an objecet that will contain a spy for both a getUser and a getRepo method. By default these will not return anything but undefined.

You can change this by doing the following:

  beforeEach(() => {
    apiService = jasmine.createSpyObj('ApiService', ['getUser', 'getRepo']);
    apiService.getRepo.and.returnValue(of({...})); // <--- data that should be returned by your api goes here

    TestBed.configureTestingModule({
      declarations: [UserProfileComponent],
      providers: [{ provide: ApiService, useValue: apiService }],
    });

    fixture = TestBed.createComponent(UserProfileComponent);
    component = fixture.componentInstance;

    // Initialize test data
    component.userDetail = { public_repos: 20 };
    component.userName = 'testuser';

    fixture.detectChanges();
  });

You can also put the line with apiService.getRepo.and.returnValue(...) inside each seperate test to change your ApiService's behavior depending on the needs in each test scenario.