🚀 Testing Azure Durable Functions with GitHub Actions: A Step-by-Step Guide

Published on: 2025-06-06
Estimated Reading Time: 7 min


Continuous Integration (CI) and Continuous Deployment (CD) are key to building reliable cloud applications. In this post, you’ll learn how to set up a GitHub Actions workflow to test Azure Durable Functions—including unit tests and integration tests—using the power of Azure Functions Core Tools and the Azurite emulator.

Let’s walk through a practical, real-world workflow you can copy, adapt, and use for your own projects!


🏗️ Workflow Structure: Two Testing Jobs

Our workflow splits testing into two logical jobs:

  1. Unit Tests – Fast feedback on your business logic.
  2. Integration Tests – Realistic end-to-end tests with a local Azure Functions runtime and emulated Azure Storage.

1️⃣ Unit Tests: Fast Feedback Loop

Unit tests run first, providing rapid feedback on your core code logic.

Example YAML

unit-tests:
  name: Unit Tests
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v3

    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '20'
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Run unit tests
      run: npm test -- --testPathIgnorePatterns=integration
      env:
        NODE_ENV: 'test'

    - name: Upload unit test results
      uses: actions/upload-artifact@v4
      with:
        name: unit-test-results
        path: |
          coverage/
          .jest-cache/

What’s Happening?

  • Checkout: Grabs your source code.
  • Node.js Setup: Uses Node 20 (make sure this matches your local dev).
  • Install & Test: Runs your usual npm ci and runs Jest or your test runner.
  • Artifact Upload: Coverage and cache files are uploaded so you can download them later from GitHub.

2️⃣ Integration Tests: Real-world Workflows

Integration tests simulate your app running locally, complete with Azure’s storage emulator (Azurite) and the Azure Functions runtime.

Example YAML

integration-tests:
  name: Integration Tests
  needs: unit-tests
  runs-on: ubuntu-latest
  services:
    azurite:
      image: mcr.microsoft.com/azure-storage/azurite
      ports:
        - 10000:10000
        - 10001:10001
        - 10002:10002
  steps:
    - uses: actions/checkout@v3

    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '20'
        cache: 'npm'

    - name: Install Azure Functions Core Tools
      run: npm install -g azure-functions-core-tools@4 --unsafe-perm true

    - name: Install dependencies
      run: npm ci

    - name: Wait for Azurite to be ready
      run: |
        for i in {1..20}; do
          nc -z localhost 10000 && echo "Azurite is up!" && break
          echo "Waiting for Azurite..."
          sleep 2
        done

    - name: Start Azure Functions Host
      run: |
        echo '{
          "IsEncrypted": false,
          "Values": {
            "AzureWebJobsStorage": "UseDevelopmentStorage=true",
            "FUNCTIONS_WORKER_RUNTIME": "node"
          }
        }' > local.settings.json

        func start --no-build --port 7071 --node &

        for i in {1..30}; do
          if curl -s http://localhost:7071/api/health > /dev/null; then
            echo "Functions host is up!"
            break
          fi
          echo "Waiting for Functions host..."
          sleep 2
        done

    - name: Run integration tests
      run: npm test -- --testPathPattern=integration
      env:
        AzureWebJobsStorage: 'UseDevelopmentStorage=true'
        FUNCTIONS_WORKER_RUNTIME: 'node'
        NODE_ENV: 'test'

    - name: Upload integration test results
      uses: actions/upload-artifact@v4
      with:
        name: integration-test-results
        path: |
          coverage/
          .jest-cache/

What’s Happening?

  • Azurite: Runs as a Docker service, simulating Azure Storage locally.
  • Waits for Azurite: Ensures the emulator is up before starting tests.
  • Azure Functions Host: Runs your Functions app locally with Core Tools.
  • Health Checks: Waits until the Functions runtime is fully started.
  • Integration Tests: Runs only integration-specific tests.
  • Artifact Upload: Again, coverage and cache are uploaded for review.

🧰 Key Ingredients

  • Node.js 20 – Modern Node for development and Functions runtime.
  • Azure Functions Core Tools v4 – Local development, running, and testing of Azure Functions.
  • Azurite – Emulator for Azure Storage accounts.
  • GitHub Actions Artifacts – Easy download of test results and coverage from each workflow run.

💡 Pro Tips and Troubleshooting

If something fails, here are some ways to diagnose:

  1. Is Azurite Running?
    curl http://localhost:10000/devstoreaccount1
    

2. **Is the Functions Host Ready?**

 ```bash
 curl http://localhost:7071/api/health
  1. Check Your Environment Variables

    echo $AzureWebJobsStorage
    echo $FUNCTIONS_WORKER_RUNTIME
    
    1. Review Logs & Artifacts
    • Look in the Actions tab of your repo for detailed logs.
    • Download coverage and cache files for offline analysis.

🎯 Summary

By structuring your workflow with clear separation of unit and integration tests, leveraging Azure emulators, and using health checks and artifact uploads, you’ll achieve robust, reliable, and repeatable testing for your Azure Durable Functions project—entirely in CI!

Now, you have a template to help your team ship with confidence 🚀 If you want this merged back into your main document, or need further edits, just let me know!