Part 7: Test Automation and CI/CD

Introduction

Automated testing in CI/CD pipelines ensures every code change is verified before reaching production. In my TypeScript microservices, automated testing catches bugs early, enables confident deployments, and maintains code quality across teams.

This final part covers practical test automation strategies, CI/CD integration, and testing workflows I use in production systems.

CI/CD Testing Strategy

The Testing Pipeline

Code Push β†’ Lint β†’ Unit Tests β†’ Integration Tests β†’ Build β†’ E2E Tests β†’ Deploy β†’ Smoke Tests

Each stage provides fast feedback and gates the next step.

GitHub Actions for Testing

Basic test workflow:

name: Test Suite

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint
      
      - name: Run TypeScript check
        run: npm run type-check

  unit-tests:
    runs-on: ubuntu-latest
    needs: lint
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run unit tests
        run: npm run test:unit -- --coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json
          flags: unit
      
      - name: Check coverage thresholds
        run: |
          npm run test:unit -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'

  integration-tests:
    runs-on: ubuntu-latest
    needs: lint
    
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run database migrations
        run: npm run db:migrate
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
      
      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json
          flags: integration

  e2e-tests:
    runs-on: ubuntu-latest
    needs: [unit-tests, integration-tests]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
      
      - name: Build application
        run: npm run build
      
      - name: Start application
        run: |
          npm start &
          npx wait-on http://localhost:3000/health --timeout 60000
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          NODE_ENV: test
      
      - name: Run E2E tests
        run: npm run test:e2e
      
      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

  build:
    runs-on: ubuntu-latest
    needs: [unit-tests, integration-tests, e2e-tests]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/

Matrix Testing

Test across multiple Node.js versions and platforms:

Parallel Testing

Speed up tests by running in parallel:

jest.config.js:

GitHub Actions parallel jobs:

Test Caching

Cache dependencies and build artifacts:

Deployment Testing

Smoke Tests After Deployment

Run smoke tests after deployment:

Test Reports

HTML Test Reports

Code Coverage Reports

Microservices Testing Strategy

Service-Level Tests

Flaky Test Detection

Security Testing in CI/CD

Best Practices

1. Fast Feedback

2. Test in Isolation

3. Retry Flaky Tests

4. Skip Tests Conditionally

5. Test Environment Parity

Monitoring Test Health

Track test metrics over time:

  • Test execution time

  • Flaky test rate

  • Coverage trends

  • Failure patterns

Key Takeaways

  1. Automate all testing - From lint to deployment

  2. Fast feedback is critical - Run fast tests first

  3. Test in isolation - Each test should be independent

  4. Parallelize when possible - Speed up test execution

  5. Monitor test health - Track flaky tests and performance

  6. Test like production - Use same services and versions

  7. Fail fast - Stop pipeline on critical failures

  8. Test coverage is important - But not the only metric

Series Conclusion

Through this series, we've covered:

  1. Testing Fundamentals - Why testing matters, testing pyramid

  2. Unit Testing - Fast, isolated tests for business logic

  3. Integration Testing - Testing component interactions

  4. API Testing - HTTP endpoints and contracts

  5. E2E Testing - Complete user workflows

  6. Performance Testing - Load, stress, and benchmarking

  7. Test Automation - CI/CD integration and continuous testing

Testing isn't optionalβ€”it's fundamental to professional software development. Start with unit tests, add integration tests for critical paths, and E2E tests for key workflows. Automate everything in CI/CD, and you'll ship with confidence.


This is the final article in the Software Testing 101 series.

Last updated