Careful about the NO_ERRORS_SCHEMA. Let's quote another part of the same the docs:
Shallow component tests with NO_ERRORS_SCHEMA greatly simplify unit testing of complex templates. However, the compiler no longer alerts you to mistakes such as misspelled or misused components and directives.
I find that drawback quite contrary to the purposes of writing a test. Even more so that it's not that hard to mock a basic component.
An approach not yet mentioned here is simply to declare them at config time:
@Component({
selector: 'product-settings',
template: '<p>Mock Product Settings Component</p>'
})
class MockProductSettingsComponent {}
@Component({
selector: 'product-editor',
template: '<p>Mock Product Editor Component</p>'
})
class MockProductEditorComponent {}
... // third one
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
ProductSelectedComponent,
MockProductSettingsComponent,
MockProductEditorComponent,
// ... third one
],
providers: [/* your providers */]
});
// ... carry on
});
Answer from Arnaud P on Stack OverflowCareful about the NO_ERRORS_SCHEMA. Let's quote another part of the same the docs:
Shallow component tests with NO_ERRORS_SCHEMA greatly simplify unit testing of complex templates. However, the compiler no longer alerts you to mistakes such as misspelled or misused components and directives.
I find that drawback quite contrary to the purposes of writing a test. Even more so that it's not that hard to mock a basic component.
An approach not yet mentioned here is simply to declare them at config time:
@Component({
selector: 'product-settings',
template: '<p>Mock Product Settings Component</p>'
})
class MockProductSettingsComponent {}
@Component({
selector: 'product-editor',
template: '<p>Mock Product Editor Component</p>'
})
class MockProductEditorComponent {}
... // third one
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
ProductSelectedComponent,
MockProductSettingsComponent,
MockProductEditorComponent,
// ... third one
],
providers: [/* your providers */]
});
// ... carry on
});
Found a nearly perfect solution, that will also correctly throw errors if someone refactors a component: https://www.npmjs.com/package/ng-mocks
npm install ng-mocks --save-dev
Now in your .spec.ts you can do
import { MockComponent } from 'ng-mocks';
import { ChildComponent } from './child.component.ts';
// ...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, RouterTestingModule],
declarations: [
ComponentUnderTest,
MockComponent(ChildComponent), // <- here is the thing
// ...
],
});
});
This creates a new anonymous Component that has the same selector, @Input() and @Output() properties of the ChildComponent, but with no code attached.
Assume that your ChildComponent has a @Input() childValue: number, that is bound in your component under test, <app-child-component [childValue]="inputValue" />
Although it has been mocked, you can use By.directive(ChildComponent) in your tests, as well as By.css('app-child-component') like so
it('sets the right value on the child component', ()=> {
component.inputValue=5;
fixture.detectChanges();
const element = fixture.debugElement.query(By.directive(ChildComponent));
expect(element).toBeTruthy();
const child: ChildComponent = element.componentInstance;
expect(child.childValue).toBe(5);
});
unit testing - angular2 test, how do I mock sub component - Stack Overflow
Is Unit Testing in Angular overrated?
What's the latest consensus of writing unit tests and mocking standalone components?
What is the best way to test child components?
Videos
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-taskcomponent.
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;
});
- Notice that because the generation of sub component of the fixture is happening asynchronously after its creation, we activate its autoDetectChanges feature.
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
» npm install mock-component
» npm install ng-mocks
» npm install ng2-mock-component