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

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:
NullInjectorError: No provider for HttpClient!(triggered byProductsService).NG0304: 'kendo-listview' is not a known element(triggered deep insideProductsListComponent).
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-devUsing 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 📊
| Strategy | Setup Complexity | Maintenance Overhead | Bug Detection Safety | Best Use Case |
|---|---|---|---|---|
NO_ERRORS_SCHEMA | Low | Low | ❌ Dangerous (Hides bugs) | Fast prototypes (avoid in production) |
| Manual Stub Components | Medium | High | ✅ Safe | Small apps with few child components |
ng-mocks (MockBuilder) | Low | Very Low | ✅ Safe & Automated | Enterprise apps, complex UI libraries |
Provider Mocking (useValue) | Low | Low | ✅ Strict | Service and dependency isolation |
Recap 🛠️
When testing Angular components with child dependencies:
- Avoid
NO_ERRORS_SCHEMAin production test suites to prevent masking real template bugs. - Use
ng-mocks(MockBuilder) for effortless, maintenance-free child component mocking. - 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!
These are my experiences learning and facing my daily challenges working with Angular.
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
How to Share Data Between Components in Angular (Signals & Services)
Learn the best patterns to share data between Angular components. Compare legacy Input/Output decorators and BehaviorSubjects with modern Signals, input(), output(), model(), and Signal-based services.
How to Use ng-template, ng-container, and ng-content in Angular (Complete Guide)
Learn the exact differences between ng-template, ng-container, and ng-content in Angular, how to pass context with ngTemplateOutlet, multi-slot projection, and how they evolve with modern @if, @for, and @defer control flow.
Angular Forms: Choosing Between Template-Driven, Reactive, and Signal-Driven Forms
A practical comparison between Template-Driven, Reactive, and Signal-Driven Forms in Angular. Learn how to build forms with strictly typed validation, modern control flow, and Signal-based computed state.
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.
Join 13,800+ developers and readers.
No spam ever. Unsubscribe at any time.