TL;DR

  • Use Terratest for integration testing GCP Terraform modules—deploy real resources, verify with GCP APIs, then destroy
  • Combine gcloud terraform vet with Policy Library constraints for pre-deployment policy validation
  • Native Terraform testing (v1.6+) handles unit tests; Terratest handles integration tests that need real GCP resources

Best for: Teams managing GCP infrastructure with Terraform who need compliance validation and deployment confidence Skip if: You’re using GCP Console exclusively or have fewer than 5 Terraform modules Read time: 12 minutes

Testing GCP infrastructure differs from AWS or Azure testing due to Google’s unique resource model, IAM structure, and the Config Validator ecosystem. This guide covers practical testing approaches that leverage GCP-native tools alongside universal infrastructure testing patterns.

Understanding the Terraform testing pyramid provides context for where GCP-specific testing fits within your overall IaC validation strategy.

AI-Assisted Approaches

AI tools accelerate GCP infrastructure test development, especially for generating constraint templates and Terratest assertions.

Generating Terratest assertions for GCP resources:

I have a Terraform module that creates:

- GCP Compute Instance with custom metadata
- VPC network with firewall rules
- Cloud Storage bucket with lifecycle policies

Generate Terratest assertions in Go that verify:

1. Instance is running and has correct machine type
2. Firewall rules allow only ports 443 and 22
3. Bucket has versioning enabled and lifecycle rule for 30-day deletion
4. All resources have required labels: environment, team, cost-center

Include proper error handling and use google.golang.org/api packages.

Creating Config Validator constraints:

Write a Rego constraint template for Config Validator that enforces:

1. All GCS buckets must have uniform bucket-level access enabled
2. All Compute instances must not have external IP addresses
3. All VPCs must have flow logs enabled

Use the GCP Policy Library format with proper CAI asset types
and include sample constraint YAML files.

Debugging failed terraform vet validations:

My terraform vet command fails with this output:
[paste vet output]

The constraint is:
[paste constraint YAML]

My Terraform plan includes:
[paste relevant terraform plan output]

Explain why the validation fails and how to fix either
the Terraform code or the constraint.

When to Use Each Testing Approach

Testing Strategy Decision Framework

Test TypeToolWhen to UseGCP Resources Required
Syntax validationterraform validateEvery commitNone
Static analysistflint-ruleset-googleEvery commitNone
Policy pre-checkgcloud terraform vetBefore plan/applyAPI access only
Unit testingTerraform test (native)Module logicNone (mocked)
Integration testingTerratestReal resource validationFull project access
End-to-endTerratest + application testsProduction-like environmentsDedicated test project

Use Terratest When

  • You need to verify actual GCP resource properties: Metadata, labels, network configs
  • Testing cross-resource interactions: IAM bindings, firewall rules affecting instances
  • Validating GCP-specific behaviors: Managed instance group scaling, Cloud SQL failover
  • CI/CD requires deployment verification: Resources must exist before proceeding

Use Config Validator/terraform vet When

  • Enforcing organizational policies: No public IPs, encryption required, specific regions
  • Pre-deployment compliance checks: Block non-compliant plans before apply
  • Standardizing across teams: Consistent tagging, naming conventions, resource configurations

Terratest for GCP Modules

Basic Module Test Structure

package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/gcp"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestGCPComputeInstance(t *testing.T) {
    t.Parallel()

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
    region := "us-central1"
    zone := "us-central1-a"

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/compute",
        Vars: map[string]interface{}{
            "project_id":    projectID,
            "region":        region,
            "zone":          zone,
            "instance_name": "test-instance-" + random.UniqueId(),
            "machine_type":  "e2-micro",
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Verify instance properties
    instanceName := terraform.Output(t, terraformOptions, "instance_name")
    instance := gcp.FetchInstance(t, projectID, zone, instanceName)

    assert.Equal(t, "RUNNING", instance.Status)
    assert.Contains(t, instance.MachineType, "e2-micro")
}

Testing VPC and Firewall Rules

func TestGCPNetworkWithFirewall(t *testing.T) {
    t.Parallel()

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/network",
        Vars: map[string]interface{}{
            "project_id":   projectID,
            "network_name": "test-vpc-" + random.UniqueId(),
            "allowed_ports": []string{"443", "22"},
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    networkName := terraform.Output(t, terraformOptions, "network_name")

    // Verify firewall rules
    firewalls := gcp.GetFirewallRulesForNetwork(t, projectID, networkName)

    for _, fw := range firewalls {
        // Ensure no rule allows 0.0.0.0/0 on sensitive ports
        for _, allowed := range fw.Allowed {
            if contains(fw.SourceRanges, "0.0.0.0/0") {
                assert.NotContains(t, allowed.Ports, "3389",
                    "RDP should not be open to internet")
                assert.NotContains(t, allowed.Ports, "3306",
                    "MySQL should not be open to internet")
            }
        }
    }
}

Testing Cloud Storage with Lifecycle Rules

func TestGCSBucketCompliance(t *testing.T) {
    t.Parallel()

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
    bucketName := "test-bucket-" + strings.ToLower(random.UniqueId())

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/storage",
        Vars: map[string]interface{}{
            "project_id":  projectID,
            "bucket_name": bucketName,
            "location":    "US",
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Use GCP client library for detailed bucket inspection
    ctx := context.Background()
    client, err := storage.NewClient(ctx)
    require.NoError(t, err)
    defer client.Close()

    bucket := client.Bucket(bucketName)
    attrs, err := bucket.Attrs(ctx)
    require.NoError(t, err)

    // Verify compliance requirements
    assert.True(t, attrs.UniformBucketLevelAccess.Enabled,
        "Uniform bucket-level access must be enabled")
    assert.True(t, attrs.VersioningEnabled,
        "Versioning must be enabled")
    assert.NotEmpty(t, attrs.Lifecycle.Rules,
        "Lifecycle rules must be configured")
}

Config Validator and Policy Library

Setting Up Policy Library

# Clone the GCP policy library
git clone https://github.com/GoogleCloudPlatform/policy-library.git
cd policy-library

# Structure
# policies/
#   constraints/    # Your constraint definitions
#   templates/      # Constraint templates (Rego rules)

Creating Custom Constraints

# policies/constraints/require_bucket_versioning.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPStorageBucketVersioningConstraintV1
metadata:
  name: require-bucket-versioning
spec:
  severity: high
  match:
    ancestries:

      - "organizations/123456789"
    excludedAncestries: []
  parameters:
    versioning_enabled: true

Constraint Template Example

# policies/templates/gcp_storage_bucket_versioning.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: gcpstoragebucketversioningconstraintv1
spec:
  crd:
    spec:
      names:
        kind: GCPStorageBucketVersioningConstraintV1
      validation:
        openAPIV3Schema:
          properties:
            versioning_enabled:
              type: boolean
  targets:

    - target: validation.gcp.forsetisecurity.org
      rego: |
        package templates.gcp.GCPStorageBucketVersioningConstraintV1

        violation[{
            "msg": message,
            "details": metadata,
        }] {
            asset := input.asset
            asset.asset_type == "storage.googleapis.com/Bucket"

            versioning := asset.resource.data.versioning
            not versioning.enabled

            message := sprintf("Bucket %v does not have versioning enabled", [asset.name])
            metadata := {"resource": asset.name}
        }

Using gcloud terraform vet

# Generate Terraform plan in JSON format
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Validate against policy library
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --project=my-project

# In CI/CD pipeline
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --format=json > violations.json

# Check for violations
if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
    echo "Policy violations found:"
    jq '.violations[] | .message' violations.json
    exit 1
fi

CI/CD Integration

GitHub Actions Workflow

name: GCP Infrastructure Tests

on:
  pull_request:
    paths:

      - 'terraform/**'
      - 'policies/**'

jobs:
  static-analysis:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.0

      - name: Terraform Validate
        run: terraform validate
        working-directory: terraform/

      - name: TFLint with GCP ruleset
        uses: terraform-linters/setup-tflint@v4
        with:
          tflint_version: latest

      - run: |
          tflint --init
          tflint --format=compact
        working-directory: terraform/

  policy-validation:
    runs-on: ubuntu-latest
    needs: static-analysis
    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@v2

      - name: Generate plan
        run: |
          terraform init
          terraform plan -out=tfplan.binary
          terraform show -json tfplan.binary > tfplan.json
        working-directory: terraform/

      - name: Policy validation
        run: |
          gcloud beta terraform vet tfplan.json \
            --policy-library=../policies \
            --format=json > violations.json

          if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
            echo "::error::Policy violations detected"
            jq -r '.violations[] | "- \(.constraint): \(.message)"' violations.json
            exit 1
          fi
        working-directory: terraform/

  integration-tests:
    runs-on: ubuntu-latest
    needs: policy-validation
    if: github.event.pull_request.draft == false
    steps:

      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Run Terratest
        run: |
          cd test
          go test -v -timeout 30m -parallel 4
        env:
          GOOGLE_PROJECT: ${{ secrets.GCP_TEST_PROJECT }}

Common GCP Testing Patterns

Testing IAM Bindings

func TestIAMBindings(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // After terraform apply...

    // Verify service account has expected roles
    saEmail := terraform.Output(t, terraformOptions, "service_account_email")

    policy, err := getProjectIAMPolicy(projectID)
    require.NoError(t, err)

    expectedRole := "roles/storage.objectViewer"
    member := "serviceAccount:" + saEmail

    found := false
    for _, binding := range policy.Bindings {
        if binding.Role == expectedRole {
            for _, m := range binding.Members {
                if m == member {
                    found = true
                    break
                }
            }
        }
    }
    assert.True(t, found, "Service account should have %s role", expectedRole)
}

Testing Private Google Access

func TestPrivateGoogleAccess(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // After creating subnet...

    subnetName := terraform.Output(t, terraformOptions, "subnet_name")
    region := "us-central1"

    subnet := gcp.FetchSubnet(t, projectID, region, subnetName)

    assert.True(t, subnet.PrivateIpGoogleAccess,
        "Private Google Access should be enabled for private subnets")
}

Measuring Success

MetricBefore TestingAfter TestingHow to Track
Policy violations in prodUnknown0Config Validator reports
Failed deployments15%/month<2%/monthCI/CD pipeline metrics
Compliance audit findings5-10/quarter0-1/quarterAudit reports
Mean time to detect issuesDaysMinutesAlert timestamps

Warning signs your testing isn’t working:

  • Policy violations still reaching production
  • Terratest passing but real issues in deployed resources
  • Tests taking >30 minutes (resource leaks likely)
  • Flaky tests due to eventual consistency

Conclusion

GCP infrastructure testing combines universal IaC testing practices with Google-specific tools:

  1. Use Terratest for integration tests requiring real GCP resources
  2. Implement Config Validator constraints for organizational policies
  3. Run gcloud terraform vet in CI/CD to catch violations early
  4. Leverage the Policy Library for common compliance patterns

The key is layering these tools—static analysis catches syntax issues, policy validation catches compliance issues, and integration tests catch functional issues.

Official Resources

See Also