Testing Observable based call hierarchy in angular - unit-testing

I am trying to test a component which uses Observables and then cascades through several function calls when the Observable resolves. Here is a version of the component.
export class NotificationComponent implements OnInit {
private answerSubscription: Subscription;
constructor(public toasterService: ToasterService, private commentService: CommentService) { }
ngOnInit() {
this.answerSubscription = this.commentService.answer$.subscribe(
answer => this.commentComplete(answer));
}
commentComplete(answer) {
this.toasterService.clear(answer.toastId);
let promptAns = this.search(answer.toastId);
}
}
and here is my test:
class MockToastService {
clear() {}
}
class MockCommentService {
answer$: Observable<any>;
constructor() {
this.answer$ = Observable.of({toastId: '123'});
}
}
describe('NotificationComponent', () => {
let component: NotificationComponent; let fixture: ComponentFixture<NotificationComponent>;
let mockComment = new MockCommentService(); let mockToast = new MockToastService();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NotificationComponent, MockToast],
providers: [{ provide: ToasterService, useValue: mockToast },
{ provide: CommentService, useValue: mockComment }]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NotificationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should complete notification on answer', () => {
spyOn(component, 'commentComplete'); spyOn(mockToast, 'clear');
expect(component.commentComplete).not.toHaveBeenCalled();
component.ngOnInit();
expect(component.commentComplete).toHaveBeenCalled();
expect(mockToast.clear).toHaveBeenCalled();
});
});
The test passes on expect(component.commentComplete).toHaveBeenCalled();, but fails on expect(mockToast.clear).toHaveBeenCalled(). As you can see from the component, toasterService.clear( should be called straight after commentComplete, however, I have stepped through with a debugger, and the test criteria is being checked before the clear function is being called.
I have tried adding fakeAsync and tick(), but am still facing the issue. Any idea how I can make this test's timing work?

You should use fake Async here but as understand there the issues was not with it.
You fake 'commentComplete' function by spyOn(component,'commentComplete') but you need to spy and do its job. change to 'spyOn(component, 'commentComplete').and.callThrough();'
Spies: and.callThrough. By chaining the spy with and.callThrough, the spy will still track all calls to it but in addition it will delegate to the actual implementation.
https://jasmine.github.io/2.0/introduction.html
here is the code that should work:
it('should complete notification on answer', fakeAsync(() => {
const spyComplete = spyOn(component, 'commentComplete').and.callThrough();
const spyToast = spyOn(mockToast, 'clear');
expect(component.commentComplete).not.toHaveBeenCalled();
component.ngOnInit();
tick();
expect(spyComplete).toHaveBeenCalled();
expect(spyToast).toHaveBeenCalled();
}));

Related

Angular 12 unit test, spy a function inside subscribe

I'm subscribing to a behavior subject in onInit and based on the result I'm calling a function. My code is like
subscription = new Subscription();
constructor(private myService: MyService) {}
ngOnInit() {
this.subscription = this.myService.event.subscribe(response => {
if(response){
this.myFunction();
}
});
}
myFunction() {}
and I'm test this by trying like below
describe('AppComponent', () => {
let event = new BehaviorSubject(false);
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
], imports: [
], providers: [{
provide: MyService, useValue: {
event: event
}
}]
}).compileComponents();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call myFunction', (done) => {
const myService = fixture.debugElement.injector.get(MyService);
myService.event.next(true);
component.ngOnInit();
const spy = spyOn(component, 'myFunction');
myService.event.subscribe((event: boolean) => {
expect(spy).toHaveBeenCalled();
done();
})
});
});
and I'm getting my spy is not called. Please help me to fix my code. Thanks a lot.
You're spying too late it seems.
Try the following:
// !! Spy first
const spy = spyOn(component, 'myFunction');
// !! Then call ngOnInit
component.ngOnInit();
Edit
Try with fakeAsync and tick.
it('should call myFunction, fakeAsync(() => {
const myService = fixture.debugElement.injector.get(MyService);
myService.event.next(true);
const spy = spyOn(component, 'myFunction');
component.ngOnInit();
tick();
expect(spy).toHaveBeenCalled();
}));
The fakeAsync/tick should hopefully wait until the subscribe is done before moving on to the expect.

How to handle bootstrap-daterangepicker in angular component unit test?

I am trying to write a unit test of an angular 6 component which is initializing the bootstrap-daterangepicker in the ngAfterViewInit() method. When I run my unit test it gives the following error:
TypeError: $(...).daterangepicker is not a function
this is the code from the actual component(EmployeeComponent):
ngAfterViewInit(): void {
this.initializeDatePicker(this);
}
initializeDatePicker(that: any) {
const start = moment().subtract(7, 'days');
const end = moment();
$('#reportrange').daterangepicker({
startDate: start,
endDate: end,
maxDate: moment(),
ranges: {
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')]
}
}, cb);
cb(start, end);
}
this is the code from my test class:
describe('EmployeeComponent', () => {
let component: EmployeeComponent;
let fixture: ComponentFixture<EmployeeComponent>;
let messageService: NotificationService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [EmployeeComponent]
})
.overrideComponent(EmployeeComponent, {
set: {
template: '',
providers: [
{ provide: NotificationService, useValue: messageService },
{ provide: ActivatedRoute, useValue: { queryParams: of({ emp: "123" }) } }
]
}
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EmployeeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
You don't need to handle it in your test cases. That component should be initialized in a separate service and you can simply mock that method from the service. In the way you can avoid this error.
let say you move all the code of the initializeDatePicker() in a method in some service let say common-service.ts and you can simply call that service from this method like
this.commonServiceObj.initializeDatePicker();
Now after doing this, you can simply mock initializeDatePicker() from the service object and error should be gone.

Mocked Service's returned observable is undefined?

I am testing an angular component, to ensure it calls a service correctly. I am mocking the service as such:
class mockSocket {
getComments() { return Observable.of(['true', 'false'])}
};
describe('CommentTableComponent', () => {
let component: CommentTableComponent;
let fixture: ComponentFixture<CommentTableComponent>;
let mockSock = new mockSocket();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CommentTableComponent],
providers: [
{ provide: SocketService, useValue: mockSock }
imports: [Ng2SmartTableModule]
})
.compileComponents();
}));
In my component's ngOnInit event, the getComments function should be called and subscribed to -
ngOnInit() {
this.socketService.getComments('api/jobs/' + line).subscribe(comment => {
// some logic
}
However, the ngOnInit is throwing error
TypeError: Cannot read property 'subscribe' of undefined
in the tests - any idea why, I am returning an Observable in the standard way?

Angular 2/Jasmine, updating an activated route params subscription within each describe/it block

Given a simple component that subscribes to the activated route query params in ngOnInit:
export class FooComponent implements OnInit {
private queryParams: any;
constructor(
private activatedRoute: ActivatedRoute
) { }
ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => this.queryParams = params);
}
active(foo: number): boolean {
return this.queryParams['foo'] &&
foo === +this.queryParams['foo'];
}
}
The active function should return true when the foo query param is present and its value matches the supplied parameter.
In the accompanying unit tests for this component, I want to change the value of the query params within each it block to test the query param not being present, matching the parameter and not matching the parameter.
describe('FooComponent', () => {
let component: FooComponent;
let fixture: ComponentFixture<FooComponent>;
let activatedRoute: ActivatedRoute;
class MockActivatedRoute {
queryParams = Observable.of({});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FooComponent],
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FooComponent);
component = fixture.componentInstance;
fixture.detectChanges();
activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
});
describe('active', () => {
it('should return false if the foo query param is not present', () => {
activatedRoute.queryParams = Observable.of({});
let result = component.active(100);
expect(result).toBe(false);
});
it('should return false if the foo query param does not match the supplied parameter', () => {
activatedRoute.queryParams = Observable.of({ foo: '500' });
let result = component.active(100);
expect(result).toBe(false);
});
it('should return true if the foo query param does not match the supplied parameter', () => {
activatedRoute.queryParams = Observable.of({ foo: '500' });
let result = component.active(500);
expect(result).toBe(true);
});
});
});
Rather the value of the private queryParams member of the FooComponent class does not update within each it block. I've tried the various methods of async, fixture.whenStable(), and fakeAsync/tick.
How do I update the value of the subscription for each unit test?
It's because you are assigning a new Observable, but the client is already subscribed to the first Observable. This happens because ngOnInit is called when you first call fixture.detectChanges(). If you waited to called fixture.detectChanges() after you assign the new Observable to the queryParams, then that Observable would be used.
Another option (maybe preferred) is to instead of using an Observable, you can use a Subject. With this, you can control when data is emitted, and what to emit.
import { Subject } from 'rxjs/Subject'
import { fakeAsync, tick } from
class MockActivatedRoute {
queryParams = new Subject<any>();
}
let route: MockActivatedRoute;
beforeEach(() => {
/* configure */
route = <MockActivatedRoute>TestBed.get(ActivatedRoute);
})
it('', fakeAsync(() => {
route.queryParams.next(newparams); // emit something
tick(); // wait for resolution
fixture.detectChanges(); // detect changes (for ui)
expect(...)
}))
I say this options might be preferred as it allows for emitting multiple values in the same test.

Angular2 final version: Injected Service method under unit test returning undefined

I am trying to write some unit-tests on a component that got some services injected into it, to load the data from server. Data is loaded in this component on OnInit() method. I am trying that service method returns some dummy data, using spyOn. Following is unit-test setup -
let comp: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let staticDataService: any;
let spy: jasmine.Spy;
let allCountries: string[];
describe('MyComponent', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
imports : [ FormsModule, HttpModule ],
declarations : [MyComponent],
providers: [ StaticDataService ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
comp = fixture.componentInstance;
staticDataService = fixture.debugElement.injector.get(StaticDataService);
allCountries = [] = ["US", "UK"];
spy = spyOn(staticDataService, 'getCountries').and.returnValue(Promise.resolve(allCountries));
});
it('Countries should be set', () => {
expect(comp.allCountries).toEqual(allCountries);
});
});
Following is the component class that I am unit-testing -
#Component({
moduleId: module.id,
selector: 'myeditor',
templateUrl: 'my.component.html',
styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
allCountries: string[];
constructor(private _staticDataServices: StaticDataService) {}
ngOnInit() {
this.getDataFromServer();
}
getDataFromServer()
{
this.allCountries = this._staticDataServices.getCountries();
}
I am getting the following error -
Chrome 53.0.2785 (Windows 7 0.0.0) MyComponent Countries should be set FAILED
[1] Expected undefined to equal [ 'US', 'UK' ].
Under the same unit-tests few other tests are working fine, that are not dependent on injected services. Getting 'undefined' while testing the properties that are set by services.
Can someone please help what I am doing wrong here?
Thanks
You need to call fixture.detectChanges() for the ngOnInit to be called.
fixture = TestBed.createComponent(MyComponent);
fixture.detectChanges();
getCountries returns a Promise so you need to then it, otherwise the value of allCountries will just be promise and not the data
getDataFromServer() {
this._staticDataServices.getCountries().then(data => {
this.countries = data;
});
}
Since the promise is asynchronous, you need to use async and wait for the asynchronous task to complete by calling fixture.whenStable()
import { async } from '#angular/core/testing';
it('...', async(() => {
fixture.whenStable().then(() => {
expect(comp.allCountries).toEqual(allCountries);
})
})
UDPATE
Without seeing the StaticDataService, I'm guessing you are trying to inject Http into it. This wont work in a test environment without further configuration. What I suggest you do is just make the service a mock
staticDataService = {
getCountries: jasmine.createSpy('getCountries').and.returnValue(...);
}
providers: [
{ provide: StaticDataService, useValue: staticDataService }
]