Angular 2 RC5 Testing Promises in ngOnInit not working

I am trying to check the MyDirective structured directive with Jasmine. The version of Angular used is RC5.

// Part of the MyDirective class @Directive({selector: '[myDirective]'}) export class MyDirective { constructor(protected templateRef: TemplateRef<any>, protected viewContainer: ViewContainerRef, protected myService: MyService) { } ngOnInit() { this.myService.getData() .then((data) => { if (!MyService.isValid(data)) { this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } }) .catch((error) => { console.log(error); this.viewContainer.createEmbeddedView(this.templateRef); }); } } 

The getData method is overwritten in the MockService class, while the isValid method (the static MyService method) is called directly, which verifies the validity of the data.

 // Part of the Jasmine unit test class for the MyDirective class @Component({ selector: 'test-cmp', template: '', directives: [MyDirective] }) class TestComponent {} class MockService { mockResponse: MyResponse = {valid date goes here}; mockInvalidResponse: MyResponse = {}; getData() { if (booleanCondition) { return Promise.resolve(this.mockResponse); } else { return Promise.resolve(this.mockInvalidResponse); } } } describe('MyDirective', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestComponent], providers: [ {provide: MyService, useClass: MockService}, TemplateRef, ViewContainerRef ] }); }); it('should remove the target DOM element when the condition is true', async(() => { booleanCondition = true; const template = '<div><div *myDirective><span>Hi</span></div></div>'; TestBed.overrideComponent(TestComponent, {set: {template: template}}); let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length).toEqual(0); })); it('should contain the target DOM element when the condition is false', async(() => { booleanCondition = false; const template = '<div><div *myDirective><span>Hi</span></div></div>'; TestBed.overrideComponent(TestComponent, {set: {template: template}}); let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); // The 'expect' bellow fails because the value is 0 for some reason expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length).toEqual(1); })); }); 

The second it should create a case where the span element is in the DOM, but it is not. I checked if this goes to the first condition in the if statement:

 if (!MyService.isValid(data)) { console.log('the first if condition is read.'); this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } } 

And he registers it. Thus, it should contain an element in the DOM, but I cannot find a way to test it.

+2
source share
1 answer

This is because a Promise (the one that returns from getData ) is asynchronous. Thus, all synchronous activity is processed before the Promise activity. Although ngOnInit is called, Promise resolves asynchronously.

There are several options that I usually use for this type.

One option is to use fakeAsync instead of async . This allows you to call tick so that asynchronous actions are executed synchronously

 import { fakeAsync, tick } from '@angular/core/testing'; it('... when the condition is false', fakeAsync(() => { const template = '<div><div *myDirective><span>Hi</span></div></div>'; TestBed.overrideComponent(TestComponent, { set: { template: template } }); let fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); // tick can also be called with a millisecond delay argument `tick(1000)` tick(); expect(getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'span').length) .toEqual(1); })); 

Another option is to make a synchronous synchronous service. You can easily do this by making the getData() call return the service itself and adding the then and catch method to the service. for instance

 class MockMyService { data; error; getData() { return this; } then(callback) { if (!this.error) { callback('mockData'); } return this; } catch(callback) { if (this.error) { callback(this.error); } } setData(data) { this.data = data; } setError(error) { this.error = error; } } 

One of the advantages of this approach is that it gives you more control over the service during the test run. It is also very useful when testing components that use templateUrl . XHR calls cannot be made in fakeAsync , so using this option is not an option. This uses a synchronous service layout.

You can either enter the service in it test cases, or simply save the variable in your test and set it up somehow

 let mockMyService: MockMyService; beforeEach(() => { mockMyService = new MockMyService(); TestBed.configureTestingModule({ providers: [ { provide: MyService, useValue: mockMyService } ] }); }); 

Note. . You will also want to correct your passing test, as the current test is not valid for the reasons mentioned above.


See also:

+3
source

Source: https://habr.com/ru/post/1269031/


All Articles