Mock hosts and testing utilities for unit testing builders and build workflows. The testing module provides comprehensive mocking capabilities to enable isolated testing of builders without requiring a real Angular workspace.
Mock host implementation for testing builders in isolation.
/**
* Testing host that provides mock workspace functionality
*/
class TestingArchitectHost implements ArchitectHost {
/**
* Mock workspace root directory
*/
workspaceRoot: string;
/**
* Mock current directory
*/
currentDirectory: string;
/**
* Create a testing host
* @param workspaceRoot - Mock workspace root directory
* @param currentDirectory - Mock current directory
* @param backendHost - Optional backend host for fallback calls
*/
constructor(
workspaceRoot?: string,
currentDirectory?: string,
backendHost?: ArchitectHost | null
);
/**
* Add a builder to the mock registry
* @param builderName - Name of the builder
* @param builder - Builder instance
* @param description - Optional builder description
* @param optionSchema - Optional option schema
*/
addBuilder(
builderName: string,
builder: Builder,
description?: string,
optionSchema?: json.schema.JsonSchema
): void;
/**
* Add a builder from an npm package
* @param packageName - Name of the package containing builders
* @returns Promise that resolves when builders are loaded
*/
addBuilderFromPackage(packageName: string): Promise<void>;
/**
* Add a target configuration
* @param target - Target specification
* @param builderName - Builder name for this target
* @param options - Default options for the target
*/
addTarget(
target: Target,
builderName: string,
options?: json.JsonObject
): void;
/**
* Set project metadata
* @param projectName - Name of the project
* @param metadata - Project metadata
* @returns This host for chaining
*/
addProject(projectName: string, metadata: json.JsonObject): this;
}Usage Examples:
import { TestingArchitectHost } from "@angular-devkit/architect/testing";
import { Architect } from "@angular-devkit/architect";
// Create testing environment
const host = new TestingArchitectHost();
const architect = new Architect(host);
// Add a builder
const mockBuilder = createBuilder((options, context) => {
return { success: true, output: options.message };
});
host.addBuilder("test-package:mock-builder", mockBuilder);
// Add target configuration
host.addTarget(
{ project: "test-app", target: "mock" },
"test-package:mock-builder",
{ message: "Hello Test" }
);
// Add project metadata
host.addProject("test-app", {
root: "apps/test-app",
projectType: "application"
});
// Test the builder
const run = await architect.scheduleTarget({
project: "test-app",
target: "mock"
});
const result = await run.result;
expect(result.success).toBe(true);
expect(result.output).toBe("Hello Test");Utility for creating mock project structures and configurations.
/**
* Host for creating test project structures
*/
class TestProjectHost {
/**
* Create a test project host
* @param workspaceRoot - Root directory for the test workspace
*/
constructor(workspaceRoot?: string);
/**
* Write a file to the mock filesystem
* @param path - File path relative to workspace root
* @param content - File content
*/
writeFile(path: string, content: string): void;
/**
* Add a target to a project
* @param project - Project name
* @param target - Target name
* @param builder - Builder name
* @param options - Target options
* @param configurations - Target configurations
*/
addTarget(
project: string,
target: string,
builder: string,
options?: json.JsonObject,
configurations?: Record<string, json.JsonObject>
): void;
/**
* Add a project configuration
* @param project - Project name
* @param config - Project configuration
*/
addProject(project: string, config: json.JsonObject): void;
/**
* Get the workspace configuration
* @returns Workspace configuration object
*/
getWorkspace(): json.JsonObject;
/**
* Get file content
* @param path - File path to read
* @returns File content as string
*/
readFile(path: string): string;
}Usage Examples:
import { TestProjectHost } from "@angular-devkit/architect/testing";
// Create test project structure
const projectHost = new TestProjectHost("/tmp/test-workspace");
// Add project files
projectHost.writeFile("package.json", JSON.stringify({
name: "test-workspace",
version: "1.0.0"
}));
projectHost.writeFile("apps/my-app/src/main.ts", `
console.log("Hello from test app");
`);
// Configure projects and targets
projectHost.addProject("my-app", {
root: "apps/my-app",
projectType: "application",
prefix: "app"
});
projectHost.addTarget(
"my-app",
"build",
"@angular-devkit/build-angular:browser",
{
outputPath: "dist/my-app",
index: "apps/my-app/src/index.html",
main: "apps/my-app/src/main.ts"
},
{
production: {
optimization: true,
sourceMap: false
},
development: {
optimization: false,
sourceMap: true
}
}
);
// Verify configuration
const workspace = projectHost.getWorkspace();
const mainContent = projectHost.readFile("apps/my-app/src/main.ts");Common patterns for testing builders with mock dependencies.
// Testing a simple builder
describe("Simple Builder", () => {
let host: TestingArchitectHost;
let architect: Architect;
beforeEach(() => {
host = new TestingArchitectHost();
architect = new Architect(host);
});
it("should build successfully", async () => {
// Create builder under test
const builder = createBuilder<{ message: string }>((options, context) => {
context.logger.info(`Building with message: ${options.message}`);
return { success: true, message: options.message };
});
// Register builder
host.addBuilder("test:simple", builder);
// Schedule and test
const run = await architect.scheduleBuilder("test:simple", {
message: "Hello World"
});
const result = await run.result;
expect(result.success).toBe(true);
expect(result.message).toBe("Hello World");
});
it("should handle errors gracefully", async () => {
const builder = createBuilder((options, context) => {
throw new Error("Test error");
});
host.addBuilder("test:error", builder);
const run = await architect.scheduleBuilder("test:error", {});
const result = await run.result;
expect(result.success).toBe(false);
expect(result.error).toContain("Test error");
});
});
// Testing builder with dependencies
describe("Builder with Dependencies", () => {
let host: TestingArchitectHost;
let architect: Architect;
beforeEach(() => {
host = new TestingArchitectHost();
architect = new Architect(host);
});
it("should run dependent builders", async () => {
// Create dependency builder
const depBuilder = createBuilder((options, context) => {
return { success: true, output: "dependency-result" };
});
// Create main builder that uses dependency
const mainBuilder = createBuilder(async (options, context) => {
const depRun = await context.scheduleBuilder("test:dependency", {});
const depResult = await depRun.result;
if (!depResult.success) {
return { success: false, error: "Dependency failed" };
}
return {
success: true,
combined: `main + ${depResult.output}`
};
});
// Register builders
host.addBuilder("test:dependency", depBuilder);
host.addBuilder("test:main", mainBuilder);
// Test main builder
const run = await architect.scheduleBuilder("test:main", {});
const result = await run.result;
expect(result.success).toBe(true);
expect(result.combined).toBe("main + dependency-result");
});
});Testing progress reporting functionality.
// Testing progress reporting
describe("Progress Reporting", () => {
let host: TestingArchitectHost;
let architect: Architect;
beforeEach(() => {
host = new TestingArchitectHost();
architect = new Architect(host);
});
it("should report progress correctly", async () => {
const progressUpdates: BuilderProgressReport[] = [];
const builder = createBuilder((options, context) => {
return new Promise((resolve) => {
let progress = 0;
const total = 5;
const interval = setInterval(() => {
progress++;
context.reportProgress(progress, total, `Step ${progress}`);
if (progress >= total) {
clearInterval(interval);
resolve({ success: true });
}
}, 10);
});
});
host.addBuilder("test:progress", builder);
const run = await architect.scheduleBuilder("test:progress", {});
// Collect progress updates
run.progress.subscribe(progress => {
progressUpdates.push(progress);
});
const result = await run.result;
expect(result.success).toBe(true);
expect(progressUpdates.length).toBeGreaterThan(0);
// Check final progress
const finalProgress = progressUpdates[progressUpdates.length - 1];
expect(finalProgress.current).toBe(5);
expect(finalProgress.total).toBe(5);
});
});Testing builders that interact with the file system.
import { TestProjectHost } from "@angular-devkit/architect/testing";
import { resolve, normalize } from "@angular-devkit/core";
describe("File System Builder", () => {
let projectHost: TestProjectHost;
let host: TestingArchitectHost;
let architect: Architect;
beforeEach(() => {
projectHost = new TestProjectHost("/tmp/test");
host = new TestingArchitectHost("/tmp/test", "/tmp/test");
architect = new Architect(host);
});
it("should process files correctly", async () => {
// Setup test files
projectHost.writeFile("src/input.txt", "Hello World");
projectHost.writeFile("src/config.json", JSON.stringify({
transform: "uppercase"
}));
// Create file processing builder
const fileBuilder = createBuilder<{ inputFile: string; outputFile: string }>(
(options, context) => {
const inputPath = resolve(normalize(context.workspaceRoot), normalize(options.inputFile));
const outputPath = resolve(normalize(context.workspaceRoot), normalize(options.outputFile));
try {
const content = require('fs').readFileSync(inputPath, 'utf8');
const processed = content.toUpperCase();
require('fs').writeFileSync(outputPath, processed);
return { success: true, inputFile: options.inputFile, outputFile: options.outputFile };
} catch (error) {
return { success: false, error: error.message };
}
}
);
host.addBuilder("test:file-processor", fileBuilder);
const run = await architect.scheduleBuilder("test:file-processor", {
inputFile: "src/input.txt",
outputFile: "dist/output.txt"
});
const result = await run.result;
expect(result.success).toBe(true);
// Verify output file was created
const outputContent = projectHost.readFile("dist/output.txt");
expect(outputContent).toBe("HELLO WORLD");
});
});Full integration tests with realistic workspace configurations.
describe("Integration Tests", () => {
let projectHost: TestProjectHost;
let host: TestingArchitectHost;
let architect: Architect;
beforeEach(() => {
projectHost = new TestProjectHost();
host = new TestingArchitectHost();
architect = new Architect(host);
// Setup realistic workspace
setupRealisticWorkspace(projectHost, host);
});
function setupRealisticWorkspace(
projectHost: TestProjectHost,
host: TestingArchitectHost
) {
// Add multiple projects
projectHost.addProject("shared-lib", {
root: "libs/shared",
projectType: "library"
});
projectHost.addProject("web-app", {
root: "apps/web",
projectType: "application"
});
// Add targets with realistic configurations
projectHost.addTarget("shared-lib", "build", "@angular-devkit/build-angular:ng-packagr", {
project: "libs/shared/ng-package.json"
});
projectHost.addTarget("web-app", "build", "@angular-devkit/build-angular:browser", {
outputPath: "dist/web-app",
index: "apps/web/src/index.html",
main: "apps/web/src/main.ts"
});
// Register actual builders (mocked)
host.addBuilder("@angular-devkit/build-angular:ng-packagr", mockLibraryBuilder);
host.addBuilder("@angular-devkit/build-angular:browser", mockAppBuilder);
}
it("should build library then application", async () => {
// Build library first
const libRun = await architect.scheduleTarget({
project: "shared-lib",
target: "build"
});
const libResult = await libRun.result;
expect(libResult.success).toBe(true);
// Build application
const appRun = await architect.scheduleTarget({
project: "web-app",
target: "build"
});
const appResult = await appRun.result;
expect(appResult.success).toBe(true);
});
});