3

I am trying to unit test a component that has a child component that I want to mock. I don't want to use the NO_ERRORS_SCHEMA cause the drawbacks associated. I am trying to use a stub/dummy child component. The code is as follows:

parent.component.ts

@Component({
  selector: 'parent',
  template: `
    <!-- Does other things -->
    <child></child>
  `
})
export class ParentComponent { }

child.component.ts

@Component({
  selector: 'child',
  template: '<!-- Does something -->'
})
export class ChildComponent {
  @Input() input1: string;
  @Input() input2?: number;
}

parent.component.spec.ts

describe('ParentComponent', () => {
  let component: ParentComponent;
  let fixture: ComponentFixture<ParentComponent>;
  let router: Router;

  @Component({
    selector: 'child',
    template: '<div></div>'
  })
  class ChildStubComponent {
    @Input() input1: string;
    @Input() input2: number;
  }

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ParentComponent, ChildStubComponent ],
      imports: [
        AppRoutingModule, HttpClientModule, BrowserAnimationsModule,
        RouterTestingModule.withRoutes(routes)
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ParentComponent);
    component = fixture.componentInstance;
    router = TestBed.get(Router);
    spyOnProperty(router, 'url', 'get').and.returnValue('/somePath');
    fixture.detectChanges();
  });

  it('should create', () => {
    component.ngOnInit();
    expect(component).toBeTruthy();
  });
});

The problem I'm having is that angular is complaining with the following error:

Failed: Template parse errors:
More than one component matched on this element.
Make sure that only one component's selector can match a given element.

I'm using Angular version 8 if that helps. Everywhere I looked so far shows that you stub a child component the same way I am doing now. Using ngMocks is not an option at the moment.

EDIT Tried using ngMock but this only has the same problem. Any help would be very much appreciated!

Solution

Got it working. Problem was that the AppRoutingModule had the real component imported already.

4
  • 1
    You probably already declared the (real) child component in your AppRoutingModule. Commented Dec 11, 2019 at 19:03
  • Is it possible to import the module without that component in there just for testing? Commented Dec 11, 2019 at 19:05
  • You can probably do it using angular.io/api/core/testing/TestBed#overrideModule. But I would just avoid importing this module in the first place. Import/declare just what you need in your testing module. Commented Dec 11, 2019 at 19:07
  • Awesome! I got it working. Thank you!! Commented Dec 11, 2019 at 19:13

3 Answers 3

2

For the nested component, we can mock them like this: https://angular.io/guide/testing-components-scenarios#stubbing-unneeded-components

In conclusion, your parent.component.spec.ts should be:

import { HttpClientTestingModule} from '@angular/common/http/testing';
import { Component } from '@angular/core';

describe('ParentComponent', () => {
  let component: ParentComponent;
  let fixture: ComponentFixture<ParentComponent>;
  let router: Router;


 @Component({selector: 'child', template: ''})
 class ChildStubComponent {}

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ParentComponent, ChildStubComponent ],
      imports: [
        AppRoutingModule, HttpClientTestingModule, BrowserAnimationsModule,
        RouterTestingModule.withRoutes(routes)
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ParentComponent);
    component = fixture.componentInstance;
    router = TestBed.get(Router);
    spyOnProperty(router, 'url', 'get').and.returnValue('/somePath');
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
Sign up to request clarification or add additional context in comments.

Comments

0

The new Angular style especially for standalone components is documented here and it boils down to using overrideComponent:

    TestBed.configureTestingModule(
      Object.assign({}, appConfig, {
        providers: [provideRouter([]), UserService],
      }),
    ).overrideComponent(AppComponent, {
      set: {
        imports: [BannerStubComponent, RouterLink, RouterOutletStubComponent, WelcomeStubComponent],
      },
    });

Note this example use set (replacing all imports), but when you have other imports you do not want to have replaced, what your actual use case/requirement may be is to replace the component (with a stub). Funnily, the solution is actually simple if you understand it once: Just remove the old component and add a new - stubbed - one:

    await TestBed.configureTestingModule({
        imports: [
          // some unrelated, not to be modified imports
        ]
    }).overrideComponent(SystemUnderTestComponent, {
        remove: { imports: [ SomeComponent ] }, // removes old "original" component
        add: { imports: [ SomeStubComponent ] } // re-adds new component
      })
      .compileComponents();

same answer as here

Comments

-3

For those wondering how it now (2025) works with standalone components, here is a nice tutorial I found out: https://youtu.be/Jv7jOrGTKd0?si=kqvGSDOzs0oA-4Vx&t=434, or text version https://www.angulararchitects.io/en/blog/testing-angular-standalone-components/

tl;dr use TestBed.overrideComponent

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.