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.
It because a
Promise(the one returned fromgetData) is asynchronous. So all the synchronous activity gets handled ahead of thePromiseactivity. Even thoughngOnInitis called, thePromiseis resolved asynchronously.There are a couple options that I usually use for this type thing.
One option is to use
fakeAsyncinstead ofasync. This allows you to calltickto allow for asynchronous actions to complete synchronouslyAnother 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 athenandcatchmethod to the service. For exampleOne 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 afakeAsync, 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
ittest cases or you can just keep a variable in you test and set it up something likeNote: You'll also want to fix your passing test, as your current test is not valid for reasons mentioned above.
See Also:
ActivatedRouteto work synchronously when testing a component.