Infrastructure testing with Pester and Azure Policy: validate IaC before and after deploy
João Barros
19 de June de 2026
1 min read
IaC (Bicep/Terraform) without tests is an unverified promise. Pester validates that the deploy created exactly what was expected; Azure Policy ensures continuous compliance even after manual changes.
Post-deploy Pester tests
BeforeAll {
Connect-AzAccount -Identity
$rg = "rg-analytics-prod"
}
Describe "Infra Analytics Prod" {
It "Storage Account exists and has ADLS Gen2 enabled" {
$sa = Get-AzStorageAccount -ResourceGroupName $rg -Name "stadatalakeprod"
$sa | Should -Not -BeNullOrEmpty
$sa.EnableHierarchicalNamespace | Should -Be $true
}
It "Key Vault has purge protection" {
$kv = Get-AzKeyVault -VaultName "kv-bconcepts-prod"
$kv.EnablePurgeProtection | Should -Be $true
}
It "ADF uses Managed Identity" {
$adf = Get-AzDataFactoryV2 -ResourceGroupName $rg -Name "adf-bconcepts-prod"
$adf.Identity.Type | Should -Be "SystemAssigned"
}
It "No NSG allows SSH (22) from the internet" {
$nsgs = Get-AzNetworkSecurityGroup -ResourceGroupName $rg
foreach ($nsg in $nsgs) {
$sshRule = $nsg.SecurityRules | Where-Object {
$_.DestinationPortRange -eq "22" -and $_.SourceAddressPrefix -eq "*"
}
$sshRule | Should -BeNullOrEmpty -Because "SSH from the internet is not allowed"
}
}
}
Azure Policy — continuous compliance
# Policy: enforce HTTPS on storage accounts
az policy assignment create \
--name "enforce-https-storage" \
--policy "Secure transfer to storage accounts should be enabled" \
--scope "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/rg-analytics-prod" \
--enforcement-mode Default # Deny — blocks non-compliant resources
# List non-compliant resources
az policy state list \
--resource-group rg-analytics-prod \
--filter "complianceState eq 'NonCompliant'" \
--query "[].{Resource:resourceId, Policy:policyDefinitionName}"
Integrate Pester into the CI/CD pipeline
- stage: PostDeploy_Tests
jobs:
- job: InfraTests
steps:
- task: PowerShell@2
inputs:
script: |
Install-Module Pester -Force -Scope CurrentUser
$result = Invoke-Pester -Path ./tests/infra -PassThru -OutputFile results.xml -OutputFormat NUnitXml
if ($result.FailedCount -gt 0) { exit 1 }
- task: PublishTestResults@2
inputs: { testResultsFiles: "results.xml" }
Conclusion
The combination of Pester (post-deploy validation) + Azure Policy (continuous compliance) closes the IaC security loop. Pester verifies that the deploy worked as expected; Policy ensures no one manually changes a resource outside the organization's standards.