Angular
·6 min read·↗

Testing Components In Angular: NO_ERRORS_SCHEMA, Stub Components, and ng-mocks

Main cover illustration for article: Testing Components In Angular: NO_ERRORS_SCHEMA, Stub Components, and ng-mocks
Summarize with AI:

When writing unit tests for isolated Angular components, testing is fast and predictable. The friction begins when your component relies on child components, which in turn depend on UI libraries (like Kendo UI or Angular Material), form modules, or HTTP services.

Suddenly, running a simple it('should create') test fails with NullInjectorError or NG0304: 'app-child' is not a known element.

⚡ Using Modern Angular (v17+)? Jump directly to Modern Angular: Testing Standalone Components with provideHttpClientTesting() to see how to configure modern TestBed setups cleanly.

In this guide, we will evaluate the three most common strategies to resolve child component dependencies in Angular tests: NO_ERRORS_SCHEMA, Manual Stub Components, and ng-mocks.

Let's start with our baseline testing scenario.


1. The Scenario: Parent Component with Deep Child Dependencies 🛍️

Imagine a parent component that displays a product catalog header and renders a child product list:

// products.component.ts (Modern Standalone)
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { ProductsService } from './products.service';
import { ProductsListComponent } from './products-list.component';
 
@Component({
  selector: 'app-products',
  standalone: true,
  imports: [AsyncPipe, ProductsListComponent],
  template: `
    <div class="products-container">
      @if (total$ | async; as total) {
        <h2>We have {{ total }} special offers today!</h2>
      }
      <app-products-list />
    </div>
  `
})
export class ProductsComponent {
  private readonly productsService = inject(ProductsService);
  readonly total$ = this.productsService.totalOffers$;
}

When you write a naive unit test with TestBed:

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [ProductsComponent]
  }).compileComponents();
});

The test runner immediately crashes with two common errors:

  1. NullInjectorError: No provider for HttpClient! (triggered by ProductsService).
  2. NG0304: 'kendo-listview' is not a known element (triggered deep inside ProductsListComponent).

Now let's examine why reaching for NO_ERRORS_SCHEMA is risky.


2. Approach 1: NO_ERRORS_SCHEMA (The Dangerous Shortcut) ⚠️

Importing NO_ERRORS_SCHEMA from @angular/core tells the Angular compiler: "Ignore any unknown HTML tag or attribute and treat it as a generic HTML element."

import { TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ProductsComponent } from './products.component';
 
beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [ProductsComponent],
    schemas: [NO_ERRORS_SCHEMA] // 👈 Suppresses template compilation errors
  }).compileComponents();
});

Why NO_ERRORS_SCHEMA is Dangerous:

  • ❌ Hides Real Template Bugs: If you misspell a component tag (<app-prodcut-list>) or bind to a non-existent property ([prduct]="item"), your tests will still pass green.
  • ❌ False Sense of Security: Reduces test coverage and lets production template errors slip into deployment.

Now let's look at a safer alternative: Manual Stub Components.


3. Approach 2: Manual Stub Components 🛡️

A stub component is a lightweight dummy class that shares the same CSS selector and inputs as the real child component, but with an empty template:

import { Component, input } from '@angular/core';
import { Product } from './product.model';
 
@Component({
  selector: 'app-products-list',
  standalone: true,
  template: '<div class="stub-products-list">Mock Products List</div>'
})
export class ProductsListStubComponent {
  readonly products = input<Product[]>([]);
}

In your test setup, override the real component with the stub:

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [ProductsComponent]
  })
  .overrideComponent(ProductsComponent, {
    remove: { imports: [ProductsListComponent] },
    add: { imports: [ProductsListStubComponent] }
  })
  .compileComponents();
});

Pros and Cons of Manual Stubs:

  • ✅ Pros: Pure isolation, no third-party UI dependencies needed, real template validation.
  • ❌ Cons: High maintenance cost. Whenever inputs or outputs change on child components, you must manually update every stub.

Now let's look at the best solution: automated mocking with ng-mocks.


4. Approach 3: Automated Mocking with ng-mocks 🚀

ng-mocks is an open-source testing utility that automatically creates type-safe mocks of components, directives, pipes, and services in a single line.

Install it with:

npm install ng-mocks --save-dev

Using MockBuilder and MockRender

Instead of manually crafting stubs or dealing with complex overrides, use MockBuilder:

import { TestBed } from '@angular/core/testing';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ProductsComponent } from './products.component';
import { ProductsListComponent } from './products-list.component';
 
describe('ProductsComponent with ng-mocks', () => {
  beforeEach(() => {
    return MockBuilder(ProductsComponent) // 1. Keep the component under test real
      .mock(ProductsListComponent)         // 2. Automatically mock the child component!
      .provide([provideHttpClient(), provideHttpClientTesting()]);
  });
 
  it('should create and render child mock cleanly', () => {
    const fixture = MockRender(ProductsComponent);
    const component = fixture.point.componentInstance;
 
    expect(component).toBeTruthy();
    // Verify that the mocked child exists in the DOM without errors
    expect(ngMocks.find(fixture, ProductsListComponent)).toBeTruthy();
  });
});

Now let's see how modern Angular (v17+) configures testing dependencies natively.


5. Modern Angular: Testing Standalone Components with provideHttpClientTesting() ⚡

In modern Angular (v17+), we no longer import HttpClientTestingModule. Instead, we use the functional provider provideHttpClientTesting() alongside provideHttpClient():

import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { of } from 'rxjs';
import { ProductsComponent } from './products.component';
import { ProductsService } from './products.service';
 
describe('ProductsComponent (Modern Standalone)', () => {
  let serviceMock: jasmine.SpyObj<ProductsService>;
 
  beforeEach(async () => {
    serviceMock = jasmine.createSpyObj('ProductsService', [], {
      totalOffers$: of(15)
    });
 
    await TestBed.configureTestingModule({
      imports: [ProductsComponent],
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        { provide: ProductsService, useValue: serviceMock }
      ]
    }).compileComponents();
  });
 
  it('should display the total number of offers from service', () => {
    const fixture = TestBed.createComponent(ProductsComponent);
    fixture.detectChanges();
 
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('h2')?.textContent).toContain('15 special offers');
  });
});

Strategy Comparison Matrix 📊

StrategySetup ComplexityMaintenance OverheadBug Detection SafetyBest Use Case
NO_ERRORS_SCHEMALowLow❌ Dangerous (Hides bugs)Fast prototypes (avoid in production)
Manual Stub ComponentsMediumHigh✅ SafeSmall apps with few child components
ng-mocks (MockBuilder)LowVery Low✅ Safe & AutomatedEnterprise apps, complex UI libraries
Provider Mocking (useValue)LowLow✅ StrictService and dependency isolation

Recap 🛠️

When testing Angular components with child dependencies:

  1. Avoid NO_ERRORS_SCHEMA in production test suites to prevent masking real template bugs.
  2. Use ng-mocks (MockBuilder) for effortless, maintenance-free child component mocking.
  3. In modern Angular (v17+), use provideHttpClientTesting() and standalone component imports for clean, fast unit tests.

For more Angular testing best practices, check out my articles on How to Share Data Between Components in Angular and How to Handle and Catch Errors in RxJS and Angular!

Part of the Angular Series

These are my experiences learning and facing my daily challenges working with Angular.

View Entire Series

Frequently Asked Questions

Why is NO_ERRORS_SCHEMA dangerous in Angular component tests?

NO_ERRORS_SCHEMA instructs the Angular compiler to suppress errors for any unrecognized HTML element or attribute in templates. While it makes tests pass immediately by ignoring child dependencies, it completely masks typos, broken property bindings, and missing component imports, producing false positives.

What is the difference between a stub component and ng-mocks MockComponent?

A manual stub component requires you to create and maintain a mock @Component class matching the original selector and inputs. ng-mocks automates this process by dynamically mocking child components, directives, and pipes in one line using MockBuilder or MockComponent.

How do you test Standalone Angular components with HttpClient in modern Angular?

In modern Angular, use 'provideHttpClient()' and 'provideHttpClientTesting()' inside your TestBed.configureTestingModule({ providers: [...] }) instead of importing the deprecated HttpClientTestingModule.

When should I use ng-mocks MockBuilder instead of TestBed?

Use ng-mocks MockBuilder when your component template relies on complex third-party UI libraries (like Kendo UI, Material, or chart widgets) and you want to test the parent component in complete isolation without importing massive dependency trees.

Related Articles

Share this article

If you found this guide helpful, consider sharing it with your team or fellow developers.


Real Software. Real Lessons.

I share the lessons I learned the hard way, so you can either avoid them or be ready when they happen.

User avatar
User avatar
User avatar
User avatar
+13K

Join 13,800+ developers and readers.

No spam ever. Unsubscribe at any time.