or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

architect.mdbuild-context.mdbuilder-creation.mdindex.mdjob-system.mdnode-integration.mdprogress-reporting.mdtesting.md
tile.json

testing.mddocs/

Testing Utilities

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.

Capabilities

Testing Architect Host

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");

Test Project Host

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");

Builder Testing Patterns

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");
  });
});

Progress Testing

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);
  });
});

Mock File System Integration

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");
  });
});

Integration Testing

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);
  });
});