Pulumi revoluciona el testing de infraestructura al permitir que los equipos usen lenguajes de programación familiares y frameworks de prueba nativos en lugar de aprender herramientas específicas de DSL. Según la encuesta Pulumi State of Cloud Engineering 2022, los equipos que implementan testing comprensivo de infraestructura reducen los fallos de despliegue en un 45% y se recuperan de incidentes un 60% más rápido. Según el Puppet State of DevOps Report 2023, las organizaciones que practican IaC con testing automatizado tienen una frecuencia de despliegue 5x mayor y una tasa de fallos 3x menor. Para los ingenieros de QA y equipos de DevOps, el enfoque de Pulumi significa que las pruebas unitarias con mocks se ejecutan 60x más rápido que las pruebas de integración — un equipo redujo su suite de 20 minutos a 20 segundos.

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 PruebaVelocidadQué ValidaCuándo se Ejecuta
Pruebas Unitarias~20 segundosLógica, configuración, propiedades de recursosCada commit
Pruebas de PropiedadesDurante despliegueCumplimiento de políticas, reglas de seguridadpulumi up
Pruebas de Integración5-30 minutosComportamiento real en la nube, flujos end-to-endPre-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.

“El testing de infraestructura con Pulumi sigue los mismos estándares de calidad que el testing de aplicaciones — las pruebas unitarias 60x más rápidas significan que no hay excusa para omitirlas.” — Yuri Kan, Senior QA Lead

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étricaLínea BaseObjetivoCómo Medir
Cobertura de pruebas unitarias0%80%+ de lógica condicionalHerramientas de cobertura (nyc, coverage.py)
Tiempo de ejecución unitariasN/A< 60 segundosMétricas del pipeline CI
Violaciones de pruebas de propiedadesDesconocido0 en producciónReportes de policy pack
Flakiness de pruebas de integraciónAlto< 5% tasa de falloAnálisis de fallos CI
Tiempo medio de detección de issuesHoras/díasMinutosTracking 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:

Recursos externos:

Recursos Oficiales

FAQ

¿En qué se diferencia el testing de Pulumi del testing de Terraform?

Pulumi usa frameworks de prueba nativos del lenguaje (Jest para TypeScript, pytest para Python) con proveedores de infraestructura simulados, por lo que las pruebas se ejecutan en milisegundos. Terraform requiere herramientas específicas de HCL como Terratest.

¿Qué son las pruebas unitarias de Pulumi y cómo se escriben?

Las pruebas unitarias de Pulumi usan pulumi.runtime.setMocks() para interceptar la creación de recursos. Escribe pruebas en tu framework nativo (Jest/pytest/go test) y verifica las propiedades de los recursos creados.

¿Qué es CrossGuard en Pulumi?

CrossGuard es el motor de políticas de Pulumi que valida recursos antes de aprovisionarlos. Define políticas en TypeScript o Python con el SDK @pulumi/policy — las reglas fallan el despliegue si hay violaciones.

¿Cuándo usar pruebas de integración vs unitarias en Pulumi?

Usa pruebas unitarias (con mocks) para validación de configuración y cumplimiento de políticas — en segundos, sin costos. Usa pruebas de integración para verificación de aprovisionamiento real. Apunta a 90% unitarias / 10% integración.

See Also