How To Mock Out Child Components In Unit Tests of Angular 2 Code

I was struggling with this one for a bit but found a solution this past weekend. When writing unit tests of a component that contains child components, the solution is to do the following:

1.      Create small mock classes in your test file to represent the child components and include just the functionality you’ll be mocking. These classes should have the @Component() decorator applied to them and contain selectors that match the real components they are being substituted for.
2.      Specify these mock components in the declarations section of the object you’re passing to TestBed.configureTestingModule().

Example:
ActivityMgmtComponent has a few child components. Here are two of them:

<activity-edit #viewEditActivity></activity-edit>
<activity-history #viewActivityHistory></activity-history>

Notice the # values – these are there to support the @ViewChild decorator function. This allows the parent component to call functions on the child components.

@ViewChild('viewActivityHistory') activityHistoryComponent: ActivityHistoryComponent;
@ViewChild('viewEditActivity') activityEditComponent: ActivityEditComponent;

And here’s an example of the parent component doing just that:

editActivity(): void {
    if (this._selectedActivity != null) {
        this.activityEditComponent.getActivity(this._selectedActivity.Id);
    }
}

In our test file, we create small, simple mocks to substitute for the real child components:

@Component({
    selector: 'activity-history',
    template: ''
})
export class ActivityHistoryMock {
    getActivityHistory = jasmine.createSpy('getActivityHistory').and.returnValue(null);
}
 
@Component({
    selector: 'activity-edit',
    template: ''
})
export class ActivityEditMock {
    getActivity = jasmine.createSpy('getActivity').and.returnValue(null);
}

They have the same selectors as the real child components (important! More on that in a bit), and we’re using Jasmine to create spies on the methods we’re mocking out.

And we wire them up in our TestBed configuration:

beforeEach(async(() => {
    TestBed.configureTestingModule({
        declarations: [
            ActivityMgmtComponent,
            ActivityDeleteComponent,
            ActivityHistoryMock, 
            ActivityEditMock
        ], 

When the parent component’s template is compiled, it sees the selectors for the real child components, but associates them with the mocks – they have the same selectors. We don’t declare the real child components, so the parent component doesn’t know about them during the test – for all it knows, these are the real deal.

And here’s one of the tests, using the mock child component in place of the real one:

it("should get activity on edit", () => {
    component.ngOnInit();
    component._selectedActivity = component._activities[0];
    component._selectedActivity.Id = "352ccb86-6898-4750-b9d4-f141f107c2d6";
    component.editActivity();
    expect(component.activityEditComponent.getActivity).toHaveBeenCalledWith(component._activities[0].Id);
});



Comments

Popular posts from this blog

A Generic Method Using HttpClient to Make a Synchronous Get Request

The Cause and Solution for the "System.Runtime.Serialization.InvalidDataContractException: Type 'System.Threading.Tasks.Task`1[YourTypeHere]' cannot be serialized." Exception