Angular 2 RC5 Testing Promises in ngOnInit Not Working

只愿长相守 提交于 2019-12-17 20:29:32

问题


I am trying to test a structural directive named MyDirective with Jasmine. The Angular version 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 whereas the isValid method (a static method of MyService) is called directly, which checks 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 is supposed to create a case in which the span element is in the DOM, but it does not. I checked to see if it goes to the first condition in the if statement like this:

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

And it logs it. So, it should keep the element in the DOM, but I can't find a way to test it.


回答1:


It because a Promise (the one returned from getData) is asynchronous. So all the synchronous activity gets handled ahead of the Promise activity. Even though ngOnInit is called, the Promise is resolved asynchronously.

There are a couple options that I usually use for this type thing.

One option is to use fakeAsync instead of async. This allows you to call tick to allow for asynchronous actions to complete 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 options is to make the mocked service synchronous. You can easily do that by making the call to getData() return the service itself, and add a then and catch method to the service. For example

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 advantage of this approach is that it gives you more control over the service during the test execution. This is also very useful when testing components that use templateUrl. XHR calls can't be made in a fakeAsync, so using that is not an option. This is where the synchronous mock service comes in use.

You can either inject the service to your it test cases or you can just keep a variable in you test and set it up something like

let mockMyService: MockMyService;

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

Note: You'll also want to fix your passing test, as your current test is not valid for reasons mentioned above.


See Also:

  • My post in Testing promise in Angular2 ngOnInit for an example of mocking ActivatedRoute to work synchronously when testing a component.


来源:https://stackoverflow.com/questions/39436230/angular-2-rc5-testing-promises-in-ngoninit-not-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!