Testing

Infrastructure is just code. Test it like code.

Why Test Infrastructure?

With r8s, your Kubernetes manifests are TypeScript components. That means you can test them with the same tools you use for your application code — Vitest, Jest, or any test runner.

No more deploying to staging and hoping it works. Run tests in CI and catch issues before they reach any cluster.

Catch Errors Early

Find missing resource limits, wrong image tags, or missing operators in CI — not in production.

Document Intent

Tests show what you expect from your infrastructure. New team members can read tests to understand the setup.

Prevent Regressions

Changing a component? Tests ensure you don't break other services that depend on it.

Enforce Standards

Pick the guardrails that matter for your organization. Start with one, add more as you grow.

Project Structure

Keep your infrastructure and tests together:

my-project/
├── k8s/
│   ├── r8s.tsx              # Your infrastructure
│   └── __tests__/
│       └── r8s.test.ts      # Tests for your infrastructure
├── package.json
└── tsconfig.json

Your Infrastructure

Define your infrastructure in k8s/r8s.tsx:

// k8s/r8s.tsx
import { App } from '@r8s/recipes';

export default (
  <App
    name="api"
    image="myapp/api:v1.2.3"
    host="api.example.com"
    replicas={3}
    database={{ name: "app-db", storage: "10Gi" }}
    tls={{ issuer: "letsencrypt" }}
    resources={{
      requests: { cpu: "100m", memory: "128Mi" },
      limits: { cpu: "500m", memory: "512Mi" },
    }}
  />
);

Test Your Infrastructure

Import your infrastructure and test it with Vitest:

// k8s/__tests__/r8s.test.ts
import { describe, it, expect } from 'vitest';
import { render } from '@r8s/core';
import infrastructure from '../r8s';

describe('Infrastructure', () => {
  it('should create a Deployment with 3 replicas', () => {
    const result = render(infrastructure);

    const deployment = result.resources.find(r => r.kind === 'Deployment');
    expect(deployment.spec.replicas).toBe(3);
  });

  it('should require CNPG operator for database', () => {
    const result = render(infrastructure);
    
    expect(result.operators).toHaveLength(1);
    expect(result.operators[0].name).toBe('cnpg');
  });
});

Built-in Guardrails

Don't write boilerplate test code. r8s includes production-ready guardrails in @r8s/core/guardrails that enforce Kubernetes best practices. Import the ones you need and run them with a single function call.

Why guardrails? Instead of writing repetitive tests like "check that every container has resource limits", use the built-in requireResourceLimits guardrail. It checks all containers across all Deployments, StatefulSets, and DaemonSets — and gives you actionable error messages.

Example: Resource Limits

The most common production issue is missing resource limits. One line prevents resource exhaustion and noisy neighbor problems:

// k8s/__tests__/guardrails.test.ts
import { describe, it, expect } from 'vitest';
import { render } from '@r8s/core';
import { runGuardrails, requireResourceLimits } from '@r8s/core/guardrails';
import infrastructure from '../r8s';

describe('Production Guardrails', () => {
  it('should have resource limits on all containers', () => {
    const result = render(infrastructure);

    const guardrails = runGuardrails(result.resources, [
      requireResourceLimits,
    ]);
    
    expect(guardrails.passed).toBe(true);
    expect(guardrails.errors).toHaveLength(0);
  });

  it('should provide actionable errors when guardrails fail', () => {
    // Simulate infrastructure without resource limits
    const result = render(
      <App name="api" image="myapp/api:v1" host="api.example.com" />
    );

    const guardrails = runGuardrails(result.resources, [
      requireResourceLimits,
    ]);
    
    expect(guardrails.passed).toBe(false);
    expect(guardrails.errors[0].code).toBe('MISSING_RESOURCE_LIMITS');
    expect(guardrails.errors[0].suggestion).toContain('resource.limits');
  });
});
// When guardrails fail, you get detailed actionable errors
{
  passed: false,
  errors: [
    {
      code: 'MISSING_RESOURCE_LIMITS',
      message: 'Container "api" in Deployment "api" is missing resource limits',
      resource: 'Deployment',
      field: 'spec.template.spec.containers[].resources.limits',
      suggestion: 'Add resource.limits with cpu and memory values to prevent resource exhaustion'
    }
  ],
  warnings: [],
  info: []
}

Combine Multiple Guardrails

Mix and match guardrails for your requirements. Each guardrail is independent — pick the ones that matter for your team:

// k8s/__tests__/guardrails.test.ts
import { describe, it, expect } from 'vitest';
import { render } from '@r8s/core';
import { 
  runGuardrails,
  requireResourceLimits,
  requireTLS,
  noRootContainers 
} from '@r8s/core/guardrails';
import infrastructure from '../r8s';

describe('Production Readiness', () => {
  it('should pass all production guardrails', () => {
    const result = render(infrastructure);

    // Combine multiple guardrails
    const guardrails = runGuardrails(result.resources, [
      requireResourceLimits,  // Prevent resource exhaustion
      requireTLS,             // Enforce HTTPS
      noRootContainers,       // Security best practice
    ]);
    
    expect(guardrails.passed).toBe(true);
    expect(guardrails.errors).toHaveLength(0);
    expect(guardrails.warnings).toHaveLength(0);
  });
});

All Available Guardrails

Import only the ones you need:

requireResourceLimits

All containers must have resource requests and limits.

requireNetworkPolicies

All namespaces must have at least one NetworkPolicy.

noPlaintextSecrets

Secrets should not contain plaintext passwords.

noRootContainers

Containers should not run as root user.

requireTLS

All Ingress resources must have TLS configured.

requireLabels

All resources must have required labels. Pass your own label names.

Custom Guardrails

Need something specific? Create your own guardrails for organizational requirements:

// k8s/__tests__/guardrails.test.ts
import { GuardrailRule, runGuardrails } from '@r8s/core/guardrails';
import { requireResourceLimits } from '@r8s/core/guardrails';
import infrastructure from '../r8s';

// Create your own guardrail
const requireReadinessProbe: GuardrailRule = {
  id: 'require-readiness-probe',
  description: 'All containers must have a readiness probe',
  severity: 'warning',
  test: (resources) => {
    const errors = [];
    
    for (const resource of resources) {
      if (resource.kind === 'Deployment') {
        const containers = resource.spec?.template?.spec?.containers || [];
        for (const container of containers) {
          if (!container.readinessProbe) {
            errors.push({
              code: 'MISSING_READINESS_PROBE',
              message: `Container "${container.name}" is missing readinessProbe`,
              resource: 'Deployment',
              field: 'spec.template.spec.containers[].readinessProbe',
              suggestion: 'Add readinessProbe to prevent traffic to unhealthy pods',
            });
          }
        }
      }
    }
    
    return errors;
  },
};

// Use it alongside built-in guardrails
const result = render(infrastructure);

const guardrails = runGuardrails(result.resources, [
  requireResourceLimits,
  requireReadinessProbe,  // Your custom rule
]);

Snapshot Testing

Use snapshots to detect unexpected changes in rendered output:

// k8s/__tests__/snapshots.test.ts
import { describe, it } from 'vitest';
import { render } from '@r8s/core';
import infrastructure from '../r8s';

describe('Snapshots', () => {
  it('should match snapshot', () => {
    const result = render(infrastructure);

    expect(result.resources).toMatchSnapshot();
    expect(result.operators).toMatchSnapshot();
  });
});

CI/CD Integration

Run tests on every push to your k8s/ folder:

name: Test Infrastructure

on:
  push:
    branches: [main]
    paths:
      - 'k8s/**'
  pull_request:
    branches: [main]
    paths:
      - 'k8s/**'

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm test

Ready to test your infrastructure?

Check out the recipes to find components to test, or read about operatorsto understand what dependencies to verify.