angular - child - ng2-mock-component
Mocking Child Components-Angular 2 (3)
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
});
How do you mock a child component when testing? I have a parent component called product-selected
whose template looks like this:
<section id="selected-container" class="container-fluid">
<hr/>
<product-settings></product-settings>
<product-editor></product-editor>
<product-options></product-options>
</section>
And the component declaration looks like this:
import { Component, Input } from '@angular/core';
import { ProductSettingsComponent } from '../settings/product-settings.component';
import { ProductEditorComponent } from '../editor/product-editor.component';
import { ProductOptionsComponent } from '../options/product-options.component';
@Component({
selector: 'product-selected',
templateUrl: './product-selected.component.html',
styleUrls: ['./product-selected.component.scss']
})
export class ProductSelectedComponent {}
This component is really just a place for the other components to live in and probably won't contain any other functions.
But when I set up the testing I get the following template error, repeated for all three components:
Error: Template parse errors:
'product-editor' is not a known element:
1. If 'product-editor' is an Angular component, then verify that it is part of this module.
2. If 'product-editor' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
<hr/>
<product-settings></product-settings>
[ERROR ->]<product-editor></product-editor>
I've tried to load a mocked version of the child components but don't know how to do - the examples that I've seen just override the parent and don't even mention the child components. So how do I go about doing it?
Found a nearly perfect solution, that will also correctly throw errors if someone refactors a component.
npm install ng-mocks
Now in your .spec.ts you can do
import { MockComponent } from 'mock-component';
import { ChildComponent } from './child.component.ts';
...
beforeEach(
async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, RouterTestingModule],
declarations: [
ComponentUnderTest,
MockComponent(ChildComponent),
...
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" />
The only downside I have found so far, is, that you can't use By.directive(ChildComponent)
in your tests, as the type changes, but instead you can use 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.css('app-child-component'));
expect(element).toBeTruthy();
const child: ChildComponent = element.componentInstance;
expect(child.childValue).toBe(5);
});
I posted this question so I could post an answer as I struggled with this for a day or two. Here's how you do it:
let declarations = [
ProductSelectedComponent,
ProductSettingsComponent,
ProductEditorComponent,
ProductOptionsComponent
];
beforeEach(() => {
TestBed.configureTestingModule({
declarations: declarations,
providers: providers
})
.overrideComponent(ProductSettingsComponent, {
set: {
selector: 'product-settings',
template: `<h6>Product Settings</h6>`
}
})
.overrideComponent(ProductEditorComponent, {
set: {
selector: 'product-editor',
template: `<h6>Product Editor</h6>`
}
})
.overrideComponent(ProductOptionsComponent, {
set: {
selector: 'product-options',
template: `<h6>Product Options</h6>`
}
});
fixture = TestBed.createComponent(ProductSelectedComponent);
cmp = fixture.componentInstance;
de = fixture.debugElement.query(By.css('section'));
el = de.nativeElement;
});
You have to chain the overrideComponent
function for each of the child components.