How do I mock sub component in jasmine tests?
I have MyComponent
, which uses MyNavbarComponent
and MyToolbarComponent
import {Component} from 'angular2/core'; import {MyNavbarComponent} from './my-navbar.component'; import {MyToolbarComponent} from './my-toolbar.component'; @Component({ selector: 'my-app', template: ` <my-toolbar></my-toolbar> {{foo}} <my-navbar></my-navbar> `, directives: [MyNavbarComponent, MyToolbarComponent] }) export class MyComponent {}
When I test this component, I do not want to load and test those two sub components; MyNavbarComponent, MyToolbarComponent, so I want to mock it.
I know how to mock with services using provide(MyService, useClass(...))
, but I have no idea how to mock directives; components;
beforeEach(() => { setBaseTestProviders( TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS ); //TODO: want to mock unnecessary directives for this component test // which are MyNavbarComponent and MyToolbarComponent }) it('should bind to {{foo}}', injectAsync([TestComponentBuilder], (tcb) => { return tcb.createAsync(MyComponent).then((fixture) => { let DOM = fixture.nativeElement; let myComponent = fixture.componentInstance; myComponent.foo = 'FOO'; fixture.detectChanges(); expect(DOM.innerHTML).toMatch('FOO'); }); });
Here is my plunker example;
http://plnkr.co/edit/q1l1y8?p=preview
A mock component in Angular tests can be created by MockComponent function. The mock component respects the interface of its original component, but all its methods are dummies. To create a mock component, simply pass its class into MockComponent function.
Introduction. Mocking is a great idea for testing Angular apps because it makes maintenance easier and helps reduce future bugs. There are a few complex tools, such as XUnit, for mocking an Angular CLI project. You can execute the mocking methods described in this guide only if you use vanilla Jasmine + Angular Testbed ...
As requested, I'm posting another answer about how to mock sub components with input
/output
:
So Lets start by saying we have TaskListComponent
that displays tasks, and refreshes whenever one of them is clicked:
<div id="task-list"> <div *ngFor="let task of (tasks$ | async)"> <app-task [task]="task" (click)="refresh()"></app-task> </div> </div>
app-task
is a sub component with the [task]
input and the (click)
output.
Ok great, now we want to write tests for my TaskListComponent
and of course we don't want to test the real app-task
component.
so as @Klas suggested we can configure our TestModule
with:
schemas: [CUSTOM_ELEMENTS_SCHEMA]
We might not get any errors at either build or runtime, but we won't be able to test much other than the existence of the sub component.
So how can we mock sub components?
First we'll define a mock directive for our sub component (same selector):
@Directive({ selector: 'app-task' }) class MockTaskDirective { @Input('task') public task: ITask; @Output('click') public clickEmitter = new EventEmitter<void>(); }
Now we'll declare it in the testing module:
let fixture : ComponentFixture<TaskListComponent>; let cmp : TaskListComponent; beforeEach(() => { TestBed.configureTestingModule({ declarations: [TaskListComponent, **MockTaskDirective**], // schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ { provide: TasksService, useClass: MockService } ] }); fixture = TestBed.createComponent(TaskListComponent); **fixture.autoDetectChanges();** cmp = fixture.componentInstance; });
In our tests, we can now query for the directive, access its DebugElement's injector, and get our mock directive instance through it:
import { By } from '@angular/platform-browser'; const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective)); const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;
[This part should usually be in the beforeEach
section, for cleaner code.]
From here, the tests are a piece of cake :)
it('should contain task component', ()=> { // Arrange. const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective)); // Assert. expect(mockTaskEl).toBeTruthy(); }); it('should pass down task object', ()=>{ // Arrange. const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective)); const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective; // Assert. expect(mockTaskCmp.task).toBeTruthy(); expect(mockTaskCmp.task.name).toBe('1'); }); it('should refresh when task is clicked', ()=> { // Arrange spyOn(cmp, 'refresh'); const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective)); const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective; // Act. mockTaskCmp.clickEmitter.emit(); // Assert. expect(cmp.refresh).toHaveBeenCalled(); });
If you use schemas: [CUSTOM_ELEMENTS_SCHEMA]
in TestBed
the component under test will not load sub components.
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TestBed, async } from '@angular/core/testing'; import { MyComponent } from './my.component'; describe('App', () => { beforeEach(() => { TestBed .configureTestingModule({ declarations: [ MyComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); }); it(`should have as title 'app works!'`, async(() => { let fixture = TestBed.createComponent(MyComponent); let app = fixture.debugElement.componentInstance; expect(app.title).toEqual('Todo List'); })); });
This works in the released version of Angular 2.0. Full code sample here.
An alternative to CUSTOM_ELEMENTS_SCHEMA
is NO_ERRORS_SCHEMA
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With