85

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?

Katana24
  • 8,706
  • 19
  • 76
  • 118

4 Answers4

115

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
});
Arnaud P
  • 12,022
  • 7
  • 56
  • 67
  • 3
    I'm using something like this for testing child components with Input and Outputs, and its great for verifying that bindings are working both ways, however, its prone to bit-rot, as a rename of an Input property in the "real" child component will not cause the test to fail. I wonder if it is possible to generate a dynamic Mock type based on the real child component by just inspecting its annotations somehow. As each child component can have an unknown set of dependency injections, it would have to be something based only on the type, not an instantiated version of the child component – Soraz Sep 11 '17 at 21:56
  • 1
    In a nutshell, I think what you need to do in that case is unit-test the child component as well. The docs have a [part dedicated to testing inputs and outputs](https://angular.io/guide/testing#test-a-component-with-inputs-and-outputs), where you can hopefully find what you need. If I understand correctly, you'll probably like [the part that directly follows](https://angular.io/guide/testing#test-a-component-inside-a-test-host-component) as well. – Arnaud P Sep 12 '17 at 07:07
  • Well, the example goes the other way. How to test a components binding against its parent. It does not help if you have a parent component with 4 child components, and you want to test the parent component in isolation, as you'd still need to either manually resolve and provide all child component dependencies, or manually maintain a Mock version of each child component. Ideally, i could split a component into two, a Base Directive that has the Input and Output, and an inherited version Component that provides the template, but that wont work as they would have to have the same selector. – Soraz Sep 15 '17 at 09:49
  • Thank you for this. Been looking all over and everyone suggests using NO_SCHEMA_ERRORS... that seems hacky to me. At least here we know what's going on – Adam Hughes Nov 02 '17 at 14:54
  • @ArnaudP, Thank you so much. Actually I did some more research on the internet and I asked some seniors personally. So, the best solution for now is: https://www.npmjs.com/package/ng-mocks It is a "Helper function for creating angular mocks for test." – Tanzeel May 13 '20 at 06:31
  • 1
    @Tanzeel nice finding ! I was going to invite you to post your own answer here, but I see Soraz has suggested this lib already. I hadn't paid attention before. – Arnaud P May 13 '20 at 09:17
68

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);
});
satanTime
  • 12,631
  • 1
  • 25
  • 73
Soraz
  • 6,610
  • 4
  • 31
  • 48
26

Generally, if you have a component used inside the view of the component that you're testing and you don't necessarily want to declare those components because they might have their own dependencies, in order to avoid the "something is not a known element" error you should use NO_ERRORS_SCHEMA.

import { NO_ERRORS_SCHEMA }          from '@angular/core';

TestBed.configureTestingModule({
        declarations: declarations,
        providers: providers
        schemas:      [ NO_ERRORS_SCHEMA ]
  })

Based on the docs :

Add NO_ERRORS_SCHEMA to the testing module's schemas metadata to tell the compiler to ignore unrecognized elements and attributes. You no longer have to declare irrelevant components and directives.

More on this : https://angular.io/docs/ts/latest/guide/testing.html#!#shallow-component-test

Liam
  • 27,717
  • 28
  • 128
  • 190
Milad
  • 27,506
  • 11
  • 76
  • 85
  • 1
    You know I was on that testing page for looking into testing Observables and never noticed that part. Thanks - it was a good read, though I wish I had of found it earlier!! Accepting your answer as it's much simpler than mine – Katana24 Dec 20 '16 at 14:15
  • 12
    this is a really poor solution because it obscures other potential issues and therefore is contrary to the purpose of testing code. Arnaud P gave a good answer. – user5080246 Aug 17 '17 at 09:45
  • 5
    Writing mock components might take more of your time but they are all worth it at the end. Using NO_ERRORS_SCHEMA will haunt you later – brijmcq Jan 19 '19 at 04:57
12

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.

Liam
  • 27,717
  • 28
  • 128
  • 190
Katana24
  • 8,706
  • 19
  • 76
  • 118
  • I think the first error is not fixed by overriding, you'd just needed to declare those components , which you've done any way , so the overriding is pointless unless you actually need it – Milad Dec 20 '16 at 12:16
  • @Milad that is true but in my case, which I haven't included, is that those components have their own dependencies on services which would then need to be included for them. Because I only care about testing this component and don't care about the others beyond a very basic creation level I can happily override them – Katana24 Dec 20 '16 at 13:23
  • Does this really override the entire component or just the override the metadata. I used overrideComponent but I still had one of my childs complaining about a missing service (only used in that child). – MortimerCat Jul 06 '18 at 14:08
  • this is the perfect answer and it makes unit testing really just testing that unit alone. – yww325 Oct 04 '21 at 17:00