Omar Abdelhady
Omar Abdelhady

Reputation: 1738

Testing a component that does not have a lifecycle hook

So I'm trying to write a test on a component that does not have a lifecycle hook.

The problem is I do not know when assigning a value directly like this without a lifecycle hook I do not know when it is exactly called and why it is not called on the component initialization

component.ts

export class MainNavComponent {
  isLoggedIn: boolean = this.authService.isLoggedIn;
  user: User;

  isHandset$: Observable<boolean> = 
    this.breakpointObserver.observe(Breakpoints.Handset)
    .pipe(
      map(result => result.matches)
    );

constructor(private breakpointObserver: BreakpointObserver,
  public authService: AuthService) {
    //check logged in user
    this.authService.user$.subscribe(res => {
      if(res) {
        this.isLoggedIn = true;
        this.user = res;
      } else {
        this.isLoggedIn = false;
      }
    })
  }
}

component.spec.ts

describe('MainNavComponent', () => {
   let component: MainNavComponent;
   let fixture: ComponentFixture<MainNavComponent>;

beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [ 
      MainNavComponent,
      FooterComponent
    ],
    imports: [
      MaterialImportsModule,
      RouterTestingModule.withRoutes([]),
      HttpClientModule,
      AngularFireModule.initializeApp(environment.firebase, 'my-app-name'),
      AngularFirestoreModule,
      AngularFireStorageModule,
      AngularFireAuthModule,
      BrowserAnimationsModule
    ] 
  })
  .compileComponents();
}));

beforeEach(() => {
  fixture = TestBed.createComponent(MainNavComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

it('assing return if user is logged in and assign it to isLoggedIn', () => {
    let service: AuthService = TestBed.get(AuthService);    
    spyOn(service, 'isLoggedIn').and.returnValue(true);

    expect(component.isLoggedIn).toBeTruthy();    
  });
});

Logged message

Expected false to be truthy.

Upvotes: 1

Views: 401

Answers (2)

Shashank Vivek
Shashank Vivek

Reputation: 17514

From what I believe, you are spying it incorrectly.

Create a Mock as

export class AuthServiceMock{
     isLoggedIn  = false;
     user$  = new BehaviorSubject<any>({user: 'Hero'});
}

and in spec file:


beforeEach(async(() => {
  TestBed.configureTestingModule({
    declarations: [ 
      MainNavComponent,
      FooterComponent
    ],
    imports: [
      MaterialImportsModule, ..........
    ],
   providers: [{provide: AuthService, useValue: AuthServiceMock}] // add this
  })
  .compileComponents();
}));

beforeEach(() => {
  fixture = TestBed.createComponent(MainNavComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
});

it('should have isLoggedIn as "true" and "user" as defined ', () => {
    expect(component.user).toBeDefined(); // or use "toEqual"
    expect(component.isLoggedIn).toBeTruthy();    
  });
});

Upvotes: 2

terahertz
terahertz

Reputation: 3511

You should shift your service assignment to beforeEach() method and add your test case to a fakeAsync() scope. You would then use tick() to simulate a passage of time for asynchronous task.

beforeEach(() => {
  fixture = TestBed.createComponent(MainNavComponent);
  component = fixture.componentInstance;
  service: AuthService = TestBed.get(AuthService); //shift service to beforeEach()
  fixture.detectChanges();
});

...

it('assing return if user is logged in and assign it to isLoggedIn', fakeAsync(() => {

  spyOn(service, 'isLoggedIn').and.returnValue(true);

  tick(); //simulate passage of time that api call went through;
  fixture.detectChanges(); //trigger change detection in Angular to ensure class's isLoggedIn is updated

  expect(service.isLoggedIn).toHaveBeenCalled();
  expect(component.isLoggedIn).toBeTruthy();
}));

Regarding tick(): https://angular.io/guide/testing#the-tick-function

Calling tick() simulates the passage of time until all pending asynchronous activities finish

On a second note, it is not advisable to use constructor to initialize service calls. Instead, it should be done in ngOnInit(). This is to ensure consistency and prevent binding errors in your templates.

Taken from Difference between Constructor and ngOnInit:

Mostly we use ngOnInit for all the initialization/declaration and avoid stuff to work in the constructor. The constructor should only be used to initialize class members but shouldn't do actual "work".

So you should use constructor() to setup Dependency Injection and not much else. ngOnInit() is better place to "start" - it's where/when components' bindings are resolved.

Upvotes: 1

Related Questions