2

When unit-testing in angular, how can I test that property in map.component.ts userRole = 'Admin'? Right now it fails on Expected undefined to equal 'Admin'. I try to mock service that gets the real data with testData. I am complete beginner in unit testing, please help. Thanks!

map.component.ts

export class MapComponent implements OnInit, OnDestroy {
userRole:string
...
constructor(
    private zone: NgZone,
    public deskDialog: MatDialog,
    private userFloorService: UserFloorService,
    private _snackBar: MatSnackBar,
    private route: ActivatedRoute,
    private router: Router
  ) {}

async ngOnInit(): Promise<void> {
    const userData = await this.userFloorService.getUser().toPromise();
    this.userRole = userData.records[0].user_role; 
    ...
}

user-floor.service.ts

export class UserFloorService {
public getUser(): Observable<any> {
    return this.http.get(
      `https://XXX`);
  }
}

map.component.spec.ts

class UserService {
  testData = {
    message: 'Succes',
    records: [
      {
        floor_id: 3,
        reservations: [
          {
            desk_id: 11,
            reservation_date: '2021-02-22',
            reservation_id: 585,
            user_id: 7,
          },
        ],
        user_gid: 'xxx',
        user_id: 7,
        user_role: 'Admin',
      },
    ],
  };
  public getUser(): Observable<any> {
    return of(this.testData);
  }
}

describe('MapComponent', () => {
  let component: MapComponent;
  let fixture: ComponentFixture<MapComponent>;
  let userService: UserFloorService;

  beforeEach(() => {
    userService = jasmine.createSpyObj(['getUser']);

    TestBed.configureTestingModule({
      declarations: [MapComponent],
      imports: [
        MatSnackBarModule,
        AppRoutingModule,
        HttpClientModule,
        BrowserModule,
        MatDialogModule,
        HttpClientTestingModule,
      ],
      providers: [{ provide: UserFloorService, useClass: UserService }],
    }).compileComponents();
    fixture = TestBed.createComponent(MapComponent);
    component = fixture.componentInstance;
    userService = TestBed.inject(UserFloorService);
    fixture.detectChanges();
  });

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

  describe('Given the component is loaded', () => {
    describe('When getUser returns mock data', () => {
      it('Then the userRole should be Admin', (done) => {
        userService.getUser().subscribe(() => {
          expect(component.userRole).toEqual('Admin');
          done();
        });
      });
    });
  });
});
hawran
  • 131
  • 3
  • 11

2 Answers2

2

First, I want to address two issues I see:

  1. Remove the HttpClientModule from your Test setup. Everything you need is provided by the HttpClientTestingModule. Same is for the spy on your UserService. Check this post for how to correctly mock the httpClient in an Unit Test: https://medium.com/netscape/testing-with-the-angular-httpclient-api-648203820712
  2. The async on your ngOnInit does not concern Angular whatsoever, it still treats it as synchronous. I personally consider using async on angular lifecycle hook a serious antipattern because you invite tons of race conditions and have to null-check etc. everything anyhow. For some reason, it keeps popping up ;) There is no gain and much pain to be had from using async there. See async/await in Angular `ngOnInit`

That being said, you should be able to achieve what you want with this using changeDetectorRef (https://angular.io/api/core/ChangeDetectorRef):

constructor(private readonly cdr: ChangeDetectorRef)

ngOnInit(): void {
 this.userFloorService.getUser().subscribe(users => {
     this.userRole = userData.records[0].user_role;
   this.cdr.detectChanges; // might require markForCheck here, try it out
  });
}

And afaik you have to manually call ngOnInit() in your test, it is not called by createComponent(...)

Doug Domeny
  • 4,410
  • 2
  • 33
  • 49
Loop
  • 480
  • 2
  • 9
1

So I was able to get it working without making changes to ngOnInit by using beforeEach with async and done() callback in it() :

describe('MapComponent', () => {
  let component: MapComponent;
  let fixture: ComponentFixture<MapComponent>;
  let userService: UserFloorService;

  beforeEach(async() => {
    await TestBed.configureTestingModule({
      declarations: [MapComponent],
      imports: [
        MatSnackBarModule,
        AppRoutingModule,
        BrowserModule,
        MatDialogModule,
        HttpClientTestingModule,
      ],
      providers: [{ provide: UserFloorService, useClass: MockService }],
    }).compileComponents();
    fixture = TestBed.createComponent(MapComponent);
    component = fixture.componentInstance;
    userService = TestBed.inject(UserFloorService);
    fixture.detectChanges();
  });

  it('should be created', () => {
    expect(component).toBeTruthy();
  });

  it('userRole should be Admin', (done) => { 
    console.log(done);
    expect(component.userRole).toEqual('Admin');
    done();
  });

});

But thanks for help, you've ponited me in the right direction.

hawran
  • 131
  • 3
  • 11