TL;DR
- La ventaja clave de Pulumi: usa los frameworks de testing nativos de tu lenguaje (Jest, pytest, Go testing) en lugar de aprender herramientas específicas de HCL
- Las pruebas unitarias con
pulumi.runtime.setMocks()son 60x más rápidas que las de integración—un equipo redujo su suite de 20 minutos a 20 segundos- Las pruebas de propiedades (CrossGuard) detectan violaciones de políticas durante el despliegue, no después—shift-left para infraestructura
Ideal para: Equipos usando TypeScript, Python o Go que quieren testing de infraestructura integrado en CI/CD Omitir si: Tienes infraestructura simple (<10 recursos) o prefieres el ecosistema de Terraform Tiempo de lectura: 11 minutos
Probar código de infraestructura en 2026 no es opcional—es lo mínimo esperado. Pero esto es lo que la mayoría de guías no te dicen: el enfoque de testing de Pulumi es fundamentalmente diferente al de Terraform, y esa diferencia es por la que las empresas están migrando.
Mientras los equipos de Terraform recurren a herramientas externas como Terratest o dependen de terraform validate, Pulumi te permite escribir pruebas en el mismo lenguaje que tu infraestructura. ¿Tu conocimiento de pytest? Funciona. ¿Tu configuración de Jest? Funciona. ¿El pipeline CI/CD de tu equipo? No necesitas plugins especiales.
La Pirámide de Testing para Infraestructura
Antes de ver código, entiende dónde encaja cada tipo de prueba:
| Tipo de Prueba | Velocidad | Qué Valida | Cuándo se Ejecuta |
|---|---|---|---|
| Pruebas Unitarias | ~20 segundos | Lógica, configuración, propiedades de recursos | Cada commit |
| Pruebas de Propiedades | Durante despliegue | Cumplimiento de políticas, reglas de seguridad | pulumi up |
| Pruebas de Integración | 5-30 minutos | Comportamiento real en la nube, flujos end-to-end | Pre-merge, nightly |
La mayoría de equipos comete este error: omiten las pruebas unitarias (“la infraestructura es diferente”) y dependen únicamente de las pruebas de integración. ¿El resultado? Ciclos de feedback lentos y pipelines de CI inestables.
Pruebas Unitarias con Mocks
El sistema de mocking de Pulumi intercepta todas las llamadas al proveedor de nube, permitiéndote probar la configuración de recursos sin provisionar nada.
TypeScript con Jest
import * as pulumi from "@pulumi/pulumi";
import "jest";
describe("S3 bucket configuration", () => {
let infra: typeof import("./index");
beforeAll(() => {
pulumi.runtime.setMocks({
newResource: (args: pulumi.runtime.MockResourceArgs) => ({
id: `${args.name}-id`,
state: {
...args.inputs,
arn: `arn:aws:s3:::${args.inputs.bucket || args.name}`,
},
}),
call: (args) => args.inputs,
});
});
beforeEach(async () => {
infra = await import("./index");
});
it("enables versioning on production buckets", (done) => {
infra.dataBucket.versioning.apply(versioning => {
expect(versioning?.enabled).toBe(true);
done();
});
});
it("blocks public access by default", (done) => {
infra.dataBucket.acl.apply(acl => {
expect(acl).toBe("private");
done();
});
});
});
Patrón crítico: Importa tu módulo de infraestructura después de configurar los mocks. El patrón beforeAll/beforeEach asegura que los mocks estén activos antes de que se instancien los recursos de Pulumi.
Python con pytest
import unittest
import pulumi
class InfraMocks(pulumi.runtime.Mocks):
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
state = {
**args.inputs,
"arn": f"arn:aws:s3:::{args.inputs.get('bucket', args.name)}",
}
return [f"{args.name}_id", state]
def call(self, args: pulumi.runtime.MockCallArgs):
return {}
pulumi.runtime.set_mocks(InfraMocks(), preview=False)
# Importar DESPUÉS de configurar los mocks
from infra import data_bucket
class TestS3Bucket(unittest.TestCase):
@pulumi.runtime.test
def test_versioning_enabled(self):
def check_versioning(versioning):
self.assertTrue(versioning.get("enabled"))
return data_bucket.versioning.apply(check_versioning)
@pulumi.runtime.test
def test_encryption_configured(self):
def check_encryption(rules):
self.assertIsNotNone(rules)
self.assertGreater(len(rules), 0)
return data_bucket.server_side_encryption_configuration.apply(
lambda c: check_encryption(c.rules) if c else self.fail("No encryption")
)
El decorador @pulumi.runtime.test maneja los tipos async Output de Pulumi, haciendo las aserciones más limpias.
Pruebas de Propiedades con CrossGuard
Las pruebas de propiedades imponen invariantes durante el despliegue. A diferencia de las pruebas unitarias que se ejecutan de forma aislada, las pruebas de propiedades ven el grafo real de recursos que Pulumi construye.
import * as policy from "@pulumi/policy";
const securityPolicies = new policy.PolicyPack("security", {
policies: [
{
name: "s3-no-public-read",
description: "S3 buckets must not allow public read access",
enforcementLevel: "mandatory",
validateResource: policy.validateResourceOfType(
aws.s3.Bucket,
(bucket, args, reportViolation) => {
if (bucket.acl === "public-read" ||
bucket.acl === "public-read-write") {
reportViolation("S3 bucket has public read access");
}
}
),
},
{
name: "ec2-approved-instance-types",
description: "EC2 instances must use approved types",
enforcementLevel: "mandatory",
validateResource: policy.validateResourceOfType(
aws.ec2.Instance,
(instance, args, reportViolation) => {
const approved = ["t3.micro", "t3.small", "t3.medium"];
if (!approved.includes(instance.instanceType)) {
reportViolation(
`Instance type ${instance.instanceType} not approved. ` +
`Use: ${approved.join(", ")}`
);
}
}
),
},
],
});
Ejecuta las políticas con: pulumi up --policy-pack ./policy
Cuándo las políticas brillan: Para imponer estándares a nivel organizacional en todos los stacks. Un policy pack, todos los equipos se benefician.
Pruebas de Integración con Automation API
Cuando necesitas verificar el comportamiento real en la nube—no solo la configuración—usa pruebas de integración con la Automation API de Pulumi.
import { LocalWorkspace } from "@pulumi/pulumi/automation";
import * as aws from "@aws-sdk/client-s3";
describe("S3 infrastructure", () => {
let stack: any;
const stackName = `test-${Date.now()}`;
beforeAll(async () => {
stack = await LocalWorkspace.createOrSelectStack({
stackName,
projectName: "s3-test",
program: async () => {
const bucket = new aws.s3.Bucket("test-bucket", {
versioning: { enabled: true },
});
return { bucketName: bucket.id };
},
});
await stack.setConfig("aws:region", { value: "us-west-2" });
await stack.up({ onOutput: console.log });
}, 300000); // 5 minutos de timeout
afterAll(async () => {
await stack.destroy({ onOutput: console.log });
await stack.workspace.removeStack(stackName);
});
it("creates a bucket with versioning", async () => {
const outputs = await stack.outputs();
const s3Client = new aws.S3Client({ region: "us-west-2" });
const versioning = await s3Client.send(
new aws.GetBucketVersioningCommand({
Bucket: outputs.bucketName.value,
})
);
expect(versioning.Status).toBe("Enabled");
});
});
Advertencia: Las pruebas de integración provisionan recursos reales. Siempre:
- Usa nombres de stack únicos con timestamps
- Implementa limpieza adecuada en
afterAll - Configura timeouts apropiados (las operaciones en la nube son lentas)
- Ejecuta en cuentas AWS aisladas o con alertas de presupuesto
Enfoques Asistidos por IA
En 2026, las herramientas de IA aceleran significativamente el testing de infraestructura. Aquí es donde destacan.
Lo que la IA hace bien:
- Generar configuraciones de mock a partir de schemas de recursos
- Escribir reglas de validación de propiedades a partir de requisitos de seguridad
- Crear variaciones de datos de prueba para casos límite
- Sugerir aserciones que podrías haber olvidado
Lo que aún necesita humanos:
- Decidir qué recursos son críticos para probar
- Entender el contexto de negocio para las reglas de validación
- Revisar las pruebas generadas por IA para evitar falsa confianza
- Diseñar la estrategia general de testing
Prompt útil para generar pruebas unitarias:
Dada esta definición de recurso Pulumi:
const bucket = new aws.s3.Bucket("data-bucket", {
versioning: { enabled: true },
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "aws:kms",
},
},
},
lifecycleRules: [{
enabled: true,
transitions: [{ days: 30, storageClass: "STANDARD_IA" }],
}],
});
Genera pruebas unitarias con Jest que verifiquen:
1. El versionado está habilitado
2. La encriptación KMS está configurada
3. Las transiciones de lifecycle están correctas
Usa el patrón setMocks de Pulumi y maneja los tipos Output correctamente.
Framework de Decisión
Cuándo Usar Pruebas Unitarias
Este enfoque funciona mejor cuando:
- Tienes lógica condicional compleja en la creación de recursos
- Múltiples entornos comparten código de infraestructura con diferentes configuraciones
- Tu equipo ya usa frameworks de testing de TypeScript/Python
- Necesitas feedback rápido en CI (< 1 minuto)
Considera alternativas cuando:
- La infraestructura es puramente declarativa sin lógica
- Estás prototipando y esperas cambios frecuentes
- El equipo es nuevo en Pulumi (empieza con pruebas de propiedades)
Cuándo Usar Pruebas de Propiedades
Este enfoque funciona mejor cuando:
- Impones seguridad/compliance en todos los stacks
- Tienes estándares organizacionales que mantener
- Los equipos operan independientemente pero comparten requisitos
- Necesitas guardrails en tiempo de despliegue
Considera alternativas cuando:
- Las políticas son específicas del stack (usa pruebas unitarias)
- Necesitas probar comportamiento real en la nube (usa integración)
- Tus políticas requieren datos externos (complejo)
Cuándo Usar Pruebas de Integración
Este enfoque funciona mejor cuando:
- Pruebas interacciones entre servicios cloud
- Validas que los permisos IAM funcionan como se espera
- Verificas conectividad de red y resolución DNS
- Smoke tests pre-producción
Considera alternativas cuando:
- Necesitas feedback rápido (usa pruebas unitarias)
- Estás probando configuración, no comportamiento (usa pruebas unitarias)
- Las restricciones de costo o tiempo son estrictas
Midiendo el Éxito
| Métrica | Línea Base | Objetivo | Cómo Medir |
|---|---|---|---|
| Cobertura de pruebas unitarias | 0% | 80%+ de lógica condicional | Herramientas de cobertura (nyc, coverage.py) |
| Tiempo de ejecución unitarias | N/A | < 60 segundos | Métricas del pipeline CI |
| Violaciones de pruebas de propiedades | Desconocido | 0 en producción | Reportes de policy pack |
| Flakiness de pruebas de integración | Alto | < 5% tasa de fallo | Análisis de fallos CI |
| Tiempo medio de detección de issues | Horas/días | Minutos | Tracking de incidentes |
Señales de alerta de que no está funcionando:
- Las pruebas unitarias pasan pero los despliegues fallan—los mocks no coinciden con la realidad
- Las pruebas de integración tardan > 30 minutos—demasiado lentas para feedback significativo
- Violaciones de propiedades descubiertas en producción—las políticas no son exhaustivas
- Alta carga de mantenimiento de tests—pruebas sobre-especificadas
Errores Comunes
1. Probar Implementación, No Comportamiento
// Mal: Probando estructura interna
expect(bucket.tags).toEqual({ Name: "my-bucket", Env: "prod" });
// Bien: Probando comportamiento significativo
expect(bucket.versioning?.enabled).toBe(true);
2. Olvidar el Unwrapping de Output
Los recursos de Pulumi devuelven Output<T>, no T. Siempre usa .apply() o el decorador de test.
# Esto fallará silenciosamente
def test_bad(self):
self.assertEqual(bucket.arn, "expected-arn") # ¡Comparando objeto Output!
# Esto funciona
@pulumi.runtime.test
def test_good(self):
return bucket.arn.apply(lambda arn: self.assertIn("s3", arn))
3. Mocks Incompletos
Si tus pruebas pasan pero los despliegues fallan, tus mocks podrían no coincidir con el comportamiento real del proveedor.
newResource: (args) => {
// Realista: incluir campos computados
if (args.type === "aws:s3/bucket:Bucket") {
return {
id: args.name,
state: {
...args.inputs,
arn: `arn:aws:s3:::${args.inputs.bucket}`,
bucketDomainName: `${args.inputs.bucket}.s3.amazonaws.com`,
region: "us-west-2",
},
};
}
// Fallback genérico
return { id: args.name, state: args.inputs };
}
Próximos Pasos
Empieza pequeño: elige un recurso crítico (tu base de datos de producción, bucket S3 principal o configuración de VPC) y escribe tres pruebas unitarias para él. Usa los patrones de Jest/pytest de arriba.
Una vez cómodo, añade un policy pack con tu requisito de seguridad principal. Eso es shift-left en acción—detectando problemas antes de que lleguen a producción.
Artículos relacionados:
- Estrategias de Testing y Validación en Terraform
- Estrategia de la Pirámide de Automatización de Tests
- Testing de Templates de CloudFormation
- Reportes de Tests en CI/CD
Recursos externos: