Lesson 21-Deno Testing, Build Tools, and Workflow Principles

Testing Framework and Practices

Testing Frameworks and Toolchain

Deno Built-in Testing Framework
Deno natively supports testing without requiring additional frameworks, using the deno test command to run tests:

// math_test.ts
import { add, subtract } from "./math.ts";
import { assertEquals } from "https://deno.land/std@0.140.0/testing/asserts.ts";

Deno.test("add function", () => {
  assertEquals(add(1, 2), 3); // Using built-in assertion
});

Deno.test("subtract function", () => {
  assertEquals(subtract(5, 3), 2);
});

// Run: deno test math_test.ts

Advanced Testing Features

// Advanced testing example
import { assertExists } from "https://deno.land/std@0.140.0/testing/asserts.ts";

Deno.test({
  name: "async operation test",
  async fn() {
    const data = await fetchData(); // Async operation
    assertExists(data);
  },
  sanitizeResources: true, // Automatically clean up resources
  sanitizeOps: true        // Automatically clean up operations
});

// Setup and teardown hooks
Deno.test("setup and teardown", () => {
  Deno.test({
    name: "test with setup",
    fn() { /* ... */ },
    setup() { /* Initialization logic */ },
    teardown() { /* Cleanup logic */ }
  });
});

Test Coverage and Reporting

Generating Coverage Reports

# Run tests and collect coverage
deno test --coverage=./coverage

# Generate HTML report
deno coverage ./coverage --lcov --output=lcov.info
genhtml lcov.info -o coverage_report

CI/CD Integration Example

# GitHub Actions example
name: Deno CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: denoland/setup-deno@v1
        with:
          deno-version: "1.35"
      - run: deno test --coverage=./coverage
      - run: deno coverage ./coverage --lcov --output=lcov.info
      - uses: codecov/codecov-action@v3
        with:
          files: lcov.info

Mocking and Dependency Injection

Manual Mocking of Dependencies

// Mocking HTTP request
import { assertEquals } from "https://deno.land/std@0.140.0/testing/asserts.ts";

Deno.test("fetch user data", async () => {
  // Save original fetch
  const originalFetch = globalThis.fetch;

  // Mock fetch
  globalThis.fetch = async () => {
    return new Response(JSON.stringify({ id: 1, name: "Mock User" }), {
      headers: { "Content-Type": "application/json" }
    });
  };

  // Execute test
  const user = await fetchUserData();
  assertEquals(user.name, "Mock User");

  // Restore original fetch
  globalThis.fetch = originalFetch;
});

Using Dependency Injection Pattern

// Testable code with dependency injection
import { assertEquals } from "https://deno.land/std@0.140.0/testing/asserts.ts";

interface HttpClient {
  get(url: string): Promise<Response>;
}

class UserService {
  constructor(private client: HttpClient) {}

  async getUser(id: string): Promise<User> {
    const resp = await this.client.get(`/users/${id}`);
    return resp.json();
  }
}

// Inject mock client during testing
Deno.test("UserService with mock", async () => {
  const mockClient = {
    get: async () => new Response(JSON.stringify({ id: "1", name: "Test" }))
  };

  const service = new UserService(mockClient);
  const user = await service.getUser("1");
  assertEquals(user.name, "Test");
});

Build Tools and Optimization

Modern Build Process Design

Using Denox for Multi-Environment Management

// denox.json configuration example
{
  "scripts": {
    "dev": "deno run --allow-net src/server.ts",
    "test": "deno test --coverage=./coverage",
    "build": "deno bundle src/main.ts dist/bundle.js",
    "start": "deno run --allow-net dist/bundle.js"
  }
}

// Run scripts
denox run dev     # Development mode
denox run build   # Production build

Code Bundling and Optimization

Advanced Bundling Configuration

// Custom bundling with Deno.Bundle API
const bundled = await Deno.emit(
  "https://deno.land/x/oak@v12.6.1/mod.ts", // Entry file
  {
    bundle: "module",
    compilerOptions: {
      // Compilation options
      lib: ["dom", "esnext"],
      target: "es2020"
    }
  }
);

// Write to file
await Deno.writeTextFile("bundle.js", bundled.files["deno:///bundle.js"]);

// Production optimization recommendations:
// 1. Use --no-check to skip type checking (when pre-checked)
// 2. Specify --import-map to reduce repeated parsing
// 3. Enable --v8-flags=--turbofan for V8 execution optimization

Tree-Shaking Practice

// Module design example (enabling tree-shaking)
// utils/math.ts
export function add(a: number, b: number) { /* ... */ }
export function subtract(a: number, b: number) { /* ... */ }

// Main entry file
import { add } from "./utils/math.ts"; // Import only add function

// Unused subtract function will be automatically removed during bundling

Performance Optimization Strategies

Caching and Incremental Builds

# Utilize Deno caching mechanism
deno cache --import-map=import_map.json src/deps.ts

# Incremental build script example
#!/bin/bash
changed_files=$(git diff --name-only HEAD^ HEAD | grep '\.ts$')
if [ -n "$changed_files" ]; then
  deno cache $changed_files
  deno bundle src/main.ts dist/bundle.js
fi

Production Environment Optimization Checklist

  1. Use --no-check to skip type checking (ensure pre-checked with deno check).
  2. Enable --import-map to reduce module resolution overhead.
  3. Configure V8 optimization flags: --v8-flags=--turbofan --v8-flags=--no-lazy.
  4. Distribute static assets via CDN.
  5. Enable HTTP/2 server push.

Workflow Automation

Development Environment Configuration

Recommended Toolchain Combination

// .vscode/settings.json
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.unstable": true,
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "denoland.vscode-deno"
}

Development Server Configuration

// Development middleware with Oak
import { Application } from "https://deno.land/x/oak@v12.6.1/mod.ts";

const app = new Application();

// Development environment middleware
if (Deno.env.get("MODE") === "development") {
  app.use(async (ctx, next) => {
    const start = Date.now();
    await next();
    const ms = Date.now() - start;
    ctx.response.headers.set("X-Response-Time", `${ms}ms`);
  });
}

// Hot reload implementation (requires tools like denon)
app.use(async (ctx) => {
  ctx.response.body = "Hello World";
});

await app.listen({ port: 8000 });

Continuous Integration Solutions

Complete GitHub Actions Example

name: Deno CI Pipeline

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: denoland/setup-deno@v1
        with:
          deno-version: "1.35"

      # Install dependencies
      - run: deno cache --import-map=import_map.json src/deps.ts

      # Run tests
      - run: deno test --allow-net --allow-read

      # Code quality checks
      - run: deno lint
      - run: deno fmt --check

      # Generate coverage
      - run: deno test --coverage=./coverage
      - run: deno coverage ./coverage --lcov --output=lcov.info
      - uses: codecov/codecov-action@v3

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: denoland/setup-deno@v1

      # Build production version
      - run: deno bundle src/main.ts dist/bundle.js

      # Deploy to server (example)
      - uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          source: "dist/"
          target: "/var/www/myapp"

Deployment Strategies and Practices

Containerized Deployment Solution

# Dockerfile example
FROM denoland/deno:1.35

# Install dependencies (leverage Deno cache layer)
WORKDIR /app
COPY import_map.json .
RUN deno cache --import-map=import_map.json src/deps.ts

# Copy source code
COPY . .

# Build production version
RUN deno bundle src/main.ts dist/bundle.js

# Run application (non-root user)
USER deno
EXPOSE 8000
CMD ["run", "--allow-net", "--allow-read", "dist/bundle.js"]

Serverless Deployment Example

# serverless.yml configuration
service: deno-serverless

provider:
  name: aws
  runtime: provided.al2
  environment:
    DENO_DIR: /tmp/deno_dir

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get

plugins:
  - serverless-deno-plugin

custom:
  deno:
    version: "1.35"
    importMap: import_map.json
    layers:
      - arn:aws:lambda:us-east-1:123456789012:layer:deno:1

Advanced Workflow Patterns

Microservices Development Pattern

Service Communication Testing Solution

// Testing microservice communication
import { assertEquals, assertMatch } from "https://deno.land/std@0.140.0/testing/asserts.ts";

Deno.test("order service integration", async () => {
  // Start payment service mock
  const paymentServer = Deno.run({
    cmd: ["deno", "run", "--allow-net", "payment_service_mock.ts"],
    stdout: "piped"
  });

  try {
    // Test order service
    const orderResponse = await fetch("http://localhost:8000/orders", {
      method: "POST",
      body: JSON.stringify({ productId: 1 })
    });

    assertEquals(orderResponse.status, 201);

    // Verify payment service call
    const paymentLogs = new TextDecoder().decode(
      await paymentServer.output()
    );
    assertMatch(paymentLogs, /Payment processed/);
  } finally {
    paymentServer.close();
  }
});

Frontend Engineering Integration

Deno with Modern Frontend Toolchain

// vite.config.js (integrating Deno type checking)
import { defineConfig } from 'vite';
import denoPlugin from 'vite-plugin-deno';

export default defineConfig({
  plugins: [
    denoPlugin({
      // Specify Deno configuration file
      config: './deno.json',
      // Enable type checking
      check: true,
      // Specify import map
      importMap: './import_map.json'
    })
  ]
});

Shared Type Definitions Solution

// shared_types.d.ts
declare interface User {
  id: string;
  name: string;
  email: string;
}

// Share this type definition between Deno and frontend
// In Deno:
// /// <reference path="./shared_types.d.ts" />
// In frontend project, import directly

Multi-Environment Configuration Management

Dynamic Environment Configuration Implementation

// config.ts
type Env = "development" | "production" | "test";

const env = Deno.env.get("DENO_ENV") as Env || "development";

const configs: Record<Env, {
  dbUrl: string;
  apiPort: number;
}> = {
  development: {
    dbUrl: "postgres://dev_user@localhost:5432/dev_db",
    apiPort: 8000
  },
  production: {
    dbUrl: Deno.env.get("DB_URL") || "",
    apiPort: 80
  },
  test: {
    dbUrl: "postgres://test_user@localhost:5432/test_db",
    apiPort: 8001
  }
};

export default configs[env];

Environment Variable Validation Solution

// env_validator.ts
const requiredVars = {
  development: ["DB_URL", "API_KEY"],
  production: ["DB_URL", "API_KEY", "SECRET"],
  test: ["DB_URL"]
};

function validateEnv() {
  const env = Deno.env.get("DENO_ENV") as string;
  const missing = requiredVars[env].filter(v => !Deno.env.get(v));

  if (missing.length > 0) {
    throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
  }
}

validateEnv();
Share your love