Reputation: 1738
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
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
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