---
title: Unit Testing
url: /docs/iac/guides/testing/unit/
---
Pulumi programs are authored in a general-purpose language like TypeScript, Python, Go, C# or Java. The full power of each language is available, including access to tools and libraries for that runtime, including testing frameworks.

When running an update, your Pulumi program talks to the Pulumi CLI to orchestrate the deployment. The idea of _unit tests_ is to cut this communication channel and replace the engine with mocks. The mocks respond to the commands from within the same OS process and return dummy data for each call that your Pulumi program makes.

Because mocks don't execute any real work, unit tests run very fast. Also, they can be made deterministic because tests don't depend on the behavior of any external system.

## Get started

Let's build a sample test suite. The example uses AWS resources, but the same capabilities and workflow apply to any Pulumi provider. To follow along, complete the [Get Started with AWS](/docs/clouds/aws/get-started/) guide to set up a basic Pulumi program in your language of choice.

Note that unit tests are supported in all [existing Pulumi runtimes](https://www.pulumi.com/docs/languages-sdks/).

## Sample program

Throughout this guide, we are testing a program that creates a simple AWS EC2-based webserver. We want to develop unit tests to ensure:

- Instances have a Name tag.
- Instances must not use an inline `userData` script—we must use a virtual machine image.
- Instances must not have SSH open to the Internet.

> **Note:** Choose a language below to adjust the contents of this guide. Your choice is applied throughout the guide.

<!-- chooser: language -->

<!-- option: typescript -->

```typescript
import * as aws from "@pulumi/aws";

export const group = new aws.ec2.SecurityGroup("web-secgrp", {
    ingress: [{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
        { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
    ],
});

const userData = `#!/bin/bash echo "Hello, World!" > index.html nohup python3 -m http.server 80 &`;

export const server = new aws.ec2.Instance("web-server-www", {
    instanceType: "t2.micro",
    securityGroups: [group.name], // reference the group object above
    ami: "ami-c55673a0", // AMI for us-east-2 (Ohio)
    userData: userData, // start a simple webserver
});

```

[View full example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-testing-webserver-typescript)

<!-- /option -->

<!-- option: python -->

```python
import pulumi
from pulumi_aws import ec2

group = ec2.SecurityGroup('web-secgrp', ingress=[{ "protocol": "tcp", "from_port": 22, "to_port": 22, "cidr_blocks": ["0.0.0.0/0"] },
    { "protocol": "tcp", "from_port": 80, "to_port": 80, "cidr_blocks": ["0.0.0.0/0"] },
])

user_data = '#!/bin/bash echo "Hello, World!" > index.html nohup python3 -m http.server 80 &'

server = ec2.Instance('web-server-www',
    instance_type="t2.micro",
    security_groups=[group.name ], # reference the group object above
    ami="ami-c55673a0",             # AMI for us-east-2 (Ohio)
    user_data=user_data)            # start a simple web server

```

[View full example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-testing-webserver-python)

<!-- /option -->

<!-- option: go -->

```go
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type infrastructure struct {
	group  *ec2.SecurityGroup
	server *ec2.Instance
}

func createInfrastructure(ctx *pulumi.Context) (*infrastructure, error) {
	group, err := ec2.NewSecurityGroup(ctx, "web-secgrp", &ec2.SecurityGroupArgs{
		Ingress: ec2.SecurityGroupIngressArray{
			ec2.SecurityGroupIngressArgs{
				Protocol:   pulumi.String("tcp"),
				FromPort:   pulumi.Int(22),
				ToPort:     pulumi.Int(22),
				CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
			},
			ec2.SecurityGroupIngressArgs{
				Protocol:   pulumi.String("tcp"),
				FromPort:   pulumi.Int(80),
				ToPort:     pulumi.Int(80),
				CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
			},
		},
	})
	if err != nil {
		return nil, err
	}

	const userData = `#!/bin/bash echo "Hello, World!" > index.html nohup python3 -m http.server 80 &`

	server, err := ec2.NewInstance(ctx, "web-server-www", &ec2.InstanceArgs{
		InstanceType:   pulumi.String("t2.micro"),
		SecurityGroups: pulumi.StringArray{group.Name}, // reference the group object above
		Ami:            pulumi.String("ami-c55673a0"),  // AMI for us-east-2 (Ohio)
		UserData:       pulumi.String(userData),        // start a simple web server
	})
	if err != nil {
		return nil, err
	}

	return &infrastructure{
		group:  group,
		server: server,
	}, nil
}

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := createInfrastructure(ctx)
		return err
	})
}

```

[View full example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-testing-webserver-go)

<!-- /option -->

<!-- option: csharp -->

```csharp
using Pulumi;
using Pulumi.Aws.Ec2;
using Pulumi.Aws.Ec2.Inputs;
using System.Threading.Tasks;

class Program
{
    static Task<int> Main() => Deployment.RunAsync<WebserverStack>();
}

public class WebserverStack : Stack
{
    public WebserverStack()
    {
        var group = new SecurityGroup("web-secgrp", new SecurityGroupArgs
        {
            Ingress =
            {
                new SecurityGroupIngressArgs { Protocol = "tcp", FromPort = 22, ToPort = 22, CidrBlocks = { "0.0.0.0/0" } },
                new SecurityGroupIngressArgs { Protocol = "tcp", FromPort = 80, ToPort = 80, CidrBlocks = { "0.0.0.0/0" } }
            }
        });

        var userData = "#!/bin/bash echo \"Hello, World!\" > index.html nohup python3 -m http.server 80 &";

        var server = new Instance("web-server-www", new InstanceArgs
        {
            InstanceType = "t2.micro",
            SecurityGroups = { group.Name }, // reference the group object above
            Ami = "ami-c55673a0",            // AMI for us-east-2 (Ohio)
            UserData = userData              // start a simple webserver
        });
    }
}

```

[View full example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-testing-webserver-csharp)

<!-- /option -->

<!-- option: java -->

```java
package myproject;

import java.util.List;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.SecurityGroup;
import com.pulumi.aws.ec2.SecurityGroupArgs;
import com.pulumi.aws.ec2.inputs.SecurityGroupIngressArgs;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var group = new SecurityGroup("web-secgrp", SecurityGroupArgs.builder()
            .ingress(
                SecurityGroupIngressArgs.builder()
                    .protocol("tcp").fromPort(22).toPort(22).cidrBlocks("0.0.0.0/0")
                    .build(),
                SecurityGroupIngressArgs.builder()
                    .protocol("tcp").fromPort(80).toPort(80).cidrBlocks("0.0.0.0/0")
                    .build())
            .build());

        var userData = "#!/bin/bash echo \"Hello, World!\" > index.html nohup python3 -m http.server 80 &";

        var server = new Instance("web-server-www", InstanceArgs.builder()
            .instanceType("t2.micro")
            .securityGroups(group.name().applyValue(List::of))  // reference the group object above
            .ami("ami-c55673a0")           // AMI for us-east-2 (Ohio)
            .userData(userData)            // start a simple web server
            .build());
    }
}

```

[View full example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-testing-webserver-java)

<!-- /option -->

<!-- option: yaml -->

```yaml
name: unit-testing-webserver-yaml
runtime: yaml
description: A sample Pulumi program used in the unit testing guide.
resources:
  web-secgrp:
    type: aws:ec2:SecurityGroup
    properties:
      ingress:
        - protocol: tcp
          fromPort: 22
          toPort: 22
          cidrBlocks: ["0.0.0.0/0"]
        - protocol: tcp
          fromPort: 80
          toPort: 80
          cidrBlocks: ["0.0.0.0/0"]
  web-server-www:
    type: aws:ec2:Instance
    properties:
      instanceType: t2.micro
      securityGroups:
        - ${web-secgrp.name}
      ami: ami-c55673a0
      userData: '#!/bin/bash echo "Hello, World!" > index.html nohup python3 -m http.server 80 &'

```

[View full example on GitHub](https://github.com/pulumi/docs/tree/master/static/programs/unit-testing-webserver-yaml)

<!-- /option -->

<!-- /chooser -->

This basic Pulumi program allocates a security group and an instance. Notice, however, that we are violating all three of the rules stated above—let's write some tests!

## Install the unit testing framework

You are free to use your favorite frameworks and libraries for writing unit tests and assertions.

<!-- chooser: language -->
<!-- option: typescript -->

This guide uses Mocha as the testing framework. [Install Mocha](https://mochajs.org/#installation) to your development environment.

```bash
npm install --global mocha
```

Then, install additional NPM modules to your program:

```bash
npm install mocha @types/mocha ts-node --global --save-dev
```

<!-- /option -->

<!-- option: python -->

We use the built-in [`unittest`](https://docs.python.org/3/library/unittest.html) framework, so no need to install anything.

<!-- /option -->

<!-- option: go -->

We use the built-in `go test` command, so no need to install anything.

<!-- /option -->

<!-- option: csharp -->

We use [NUnit](https://nunit.org/) test framework to define and run the tests, [Moq](https://github.com/moq/moq4) for mocks, and [FluentAssertions](https://github.com/fluentassertions/fluentassertions) for assertions.

Install the corresponding NuGet packages to your program:

```bash
dotnet add package NUnit
dotnet add package NUnit3TestAdapter
dotnet add package Moq
dotnet add package FluentAssertions
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package Pulumi
dotnet add package Pulumi.Aws
```

<!-- /option -->

<!-- option: java -->

This guide uses [JUnit 5](https://junit.org/junit5/) as the testing framework. Add the following dependencies to your `pom.xml`:

```xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    junit-jupiter-api
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    junit-jupiter-engine
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not have a general-purpose language runtime, so mock-based unit testing is not applicable. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to validate your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

## Add mocks

Let's add the following code to mock the external calls to the Pulumi CLI.

<!-- chooser: language -->
<!-- option: typescript -->

ec2tests.ts:

```typescript
import * as pulumi from "@pulumi/pulumi";

pulumi.runtime.setMocks({
    newResource: function(args: pulumi.runtime.MockResourceArgs): {id: string, state: any} {
        switch (args.type) {
            case "aws:ec2/securityGroup:SecurityGroup":
                return {
                    id: "sg-12345678",
                    state: {
                        ...args.inputs,
                        // Mock output properties that may be used in tests
                        arn: "arn:aws:ec2:us-west-2:123456789012:security-group/sg-12345678",
                        name: args.inputs.name || args.name + "-sg",
                    },
                };
            case "aws:ec2/instance:Instance":
                return {
                    id: "i-1234567890abcdef0",
                    state: {
                        ...args.inputs,
                        // Mock output properties that may be used in tests
                        arn: "arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0",
                        instanceState: "running",
                        primaryNetworkInterfaceId: "eni-12345678",
                        privateDns: "ip-10-0-1-17.ec2.internal",
                        publicDns: "ec2-203-0-113-12.compute-1.amazonaws.com",
                        publicIp: "203.0.113.12",
                    },
                };
            default:
                return {
                    id: args.inputs.name + "_id",
                    state: {
                        ...args.inputs,
                    },
                };
        }
    },
    call: function(args: pulumi.runtime.MockCallArgs) {
        switch (args.token) {
            case "aws:ec2/getAmi:getAmi":
                return {
                    "architecture": "x86_64",
                    "id": "ami-0eb1f3cdeeb8eed2a",
                };
            default:
                return args.inputs;
        }
    },
},
  "project",
  "stack",
  false, // Sets the flag `dryRun`, which indicates if pulumi is running in preview mode.
);
```

The mock implementation uses a `switch` statement to return different properties based on resource type. This is important because:

- **Input properties** (like `tags`, `userData`, `ingress`) are set by your code and passed via `args.inputs`
- **Output properties** (like `arn`, `publicIp`, `instanceState`) are computed by the cloud provider and need to be mocked explicitly

The tests shown later in this guide access both input properties (spread from `args.inputs`) and output properties (like `arn` and `publicIp`). Without mocking these output properties, they would be `undefined` in your tests.

<!-- /option -->

<!-- option: python -->

test_ec2.py:

```python
import pulumi

class MyMocks(pulumi.runtime.Mocks):
    def new_resource(self, args: pulumi.runtime.MockResourceArgs):
        return [args.name + '_id', args.inputs]
    def call(self, args: pulumi.runtime.MockCallArgs):
        return {}

pulumi.runtime.set_mocks(
    MyMocks(),
    preview=False,  # Sets the flag `dry_run`, which is true at runtime during a preview.
)
```

> **Warning:** When returning explicit output properties from `new_resource`, property names must use camelCase (e.g., `"publicIp"`, `"instanceState"`) rather than snake_case. This is because Pulumi uses camelCase for its internal property serialization regardless of the programming language. For example, use `"publicIp"` rather than `"public_ip"`.

<!-- /option -->

<!-- option: go -->

main_test.go:

```go
import (
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type mocks int

func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
	return args.Name + "_id", args.Inputs, nil
}

func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
	return args.Args, nil
}
```

<!-- /option -->

<!-- option: csharp -->

Testing.cs:

```csharp
using System.Collections.Immutable;
using System.Threading.Tasks;
using Pulumi;
using Pulumi.Testing;

namespace UnitTesting
{
    class Mocks : IMocks
    {
        public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args)
        {
            var outputs = ImmutableDictionary.CreateBuilder<string, object>();

            outputs.AddRange(args.Inputs);

            if (args.Type == "aws:ec2/instance:Instance")
            {
                outputs.Add("publicIp", "203.0.113.12");
                outputs.Add("publicDns", "ec2-203-0-113-12.compute-1.amazonaws.com");
            }

            args.Id ??= $"{args.Name}_id";
            return Task.FromResult<(string? id, object state)>((args.Id, (object)outputs));
        }

        public Task<object> CallAsync(MockCallArgs args)
        {
            var outputs = ImmutableDictionary.CreateBuilder<string, object>();

            if (args.Token == "aws:index/getAmi:getAmi")
            {
                outputs.Add("architecture", "x86_64");
                outputs.Add("id", "ami-0eb1f3cdeeb8eed2a");
            }

            return Task.FromResult((object)outputs);
        }
    }

    public static class Testing
    {
        public static Task<ImmutableArray<Resource>> RunAsync<T>() where T : Stack, new()
        {
            return Deployment.TestAsync<T>(new Mocks(), new TestOptions { IsPreview = false });
        }

        public static Task<T> GetValueAsync<T>(this Output<T> output)
        {
            var tcs = new TaskCompletionSource<T>();
            output.Apply(v =>
            {
                tcs.SetResult(v);
                return v;
            });
            return tcs.Task;
        }
    }
}
```

<!-- /option -->

<!-- option: java -->

Ec2Tests.java:

```java
package myproject;

import com.pulumi.test.Mocks;
import com.pulumi.test.Mocks.CallArgs;
import com.pulumi.test.Mocks.ResourceArgs;
import com.pulumi.test.Mocks.ResourceResult;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

class MyMocks implements Mocks {
    @Override
    public CompletableFuture<ResourceResult> newResourceAsync(ResourceArgs args) {
        var state = new HashMap<>(args.inputs);
        return CompletableFuture.completedFuture(
            ResourceResult.of(Optional.of(args.name + "_id"), state)
        );
    }

    @Override
    public CompletableFuture<Map<String, Object>> callAsync(CallArgs args) {
        return CompletableFuture.completedFuture(Map.of());
    }
}
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to validate your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

The definition of the mocks interface is available at the [runtime API reference page](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/pulumi/runtime/#Mocks).

## Mocking stack references

If your program uses [StackReference](/docs/concepts/stacks/#stackreferences) to read outputs from another stack, you need to handle them in your mocks. When a `StackReference` resource is created, the mock's `newResource` function receives it with type `pulumi:pulumi:StackReference`. You can return mock outputs that simulate the referenced stack's outputs.

<!-- chooser: language -->
<!-- option: typescript -->

```typescript
pulumi.runtime.setMocks({
    newResource: function(args: pulumi.runtime.MockResourceArgs): {id: string, state: any} {
        // Handle StackReference resources
        if (args.type === "pulumi:pulumi:StackReference") {
            return {
                id: args.inputs.name + "_id",
                state: {
                    ...args.inputs,
                    outputs: {
                        // Mock the outputs from the referenced stack
                        vpcId: "vpc-12345678",
                        subnetIds: ["subnet-11111111", "subnet-22222222"],
                        clusterName: "my-cluster",
                    },
                },
            };
        }
        // Handle all other resources
        return {
            id: args.inputs.name + "_id",
            state: args.inputs,
        };
    },
    call: function(args: pulumi.runtime.MockCallArgs) {
        return args.inputs;
    },
});
```

In your test, you can then verify that values from the stack reference are used correctly:

```typescript
// Example: Program that reads from a StackReference
const networkStack = new pulumi.StackReference("organization/network/prod");
const vpcId = networkStack.getOutput("vpcId");

// In tests, vpcId will resolve to "vpc-12345678" based on the mock above
```

<!-- /option -->

<!-- option: python -->

```python
import pulumi

class MyMocks(pulumi.runtime.Mocks):
    def new_resource(self, args: pulumi.runtime.MockResourceArgs):
        # Handle StackReference resources
        if args.typ == "pulumi:pulumi:StackReference":
            return [args.name + "_id",
                {
                    **args.inputs,
                    "outputs": {
                        # Mock the outputs from the referenced stack
                        "vpcId": "vpc-12345678",
                        "subnetIds": ["subnet-11111111", "subnet-22222222"],
                        "clusterName": "my-cluster",
                    },
                },
            ]
        # Handle all other resources
        return [args.name + "_id", args.inputs]

    def call(self, args: pulumi.runtime.MockCallArgs):
        return {}

pulumi.runtime.set_mocks(MyMocks())
```

In your program, you can then use a `StackReference` as usual:

```python
network_stack = pulumi.StackReference("organization/network/prod")
vpc_id = network_stack.get_output("vpcId")

# In tests, vpc_id will resolve to "vpc-12345678" based on the mock above
```

<!-- /option -->

<!-- option: go -->

```go
type mocks int

func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) {
	// Handle StackReference resources
	if args.TypeToken == "pulumi:pulumi:StackReference" {
		outputs := resource.NewPropertyMapFromMap(map[string]interface{}{
			"vpcId":       "vpc-12345678",
			"subnetIds":   []interface{}{"subnet-11111111", "subnet-22222222"},
			"clusterName": "my-cluster",
		})
		// Copy inputs and add outputs
		state := args.Inputs.Copy()
		state["outputs"] = resource.NewObjectProperty(outputs)
		return args.Name + "_id", state, nil
	}
	// Handle all other resources
	return args.Name + "_id", args.Inputs, nil
}

func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) {
	return args.Args, nil
}
```

In your program, you can then use a `StackReference` as usual:

```go
networkStack, err := pulumi.NewStackReference(ctx, "organization/network/prod", nil)
if err != nil {
    return err
}
vpcId := networkStack.GetStringOutput(pulumi.String("vpcId"))

// In tests, vpcId will resolve to "vpc-12345678" based on the mock above
```

<!-- /option -->

<!-- option: csharp -->

```csharp
class Mocks : IMocks
{
    public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args)
    {
        var outputs = ImmutableDictionary.CreateBuilder<string, object>();
        outputs.AddRange(args.Inputs);

        // Handle StackReference resources
        if (args.Type == "pulumi:pulumi:StackReference")
        {
            outputs.Add("outputs", new Dictionary<string, object>
            {
                // Mock the outputs from the referenced stack
                { "vpcId", "vpc-12345678" },
                { "subnetIds", new[] { "subnet-11111111", "subnet-22222222" } },
                { "clusterName", "my-cluster" },
            });
        }

        args.Id ??= $"{args.Name}_id";
        return Task.FromResult<(string? id, object state)>((args.Id, (object)outputs));
    }

    public Task<object> CallAsync(MockCallArgs args)
    {
        return Task.FromResult((object)ImmutableDictionary<string, object>.Empty);
    }
}
```

In your program, you can then use a `StackReference` as usual:

```csharp
var networkStack = new StackReference("organization/network/prod");
var vpcId = networkStack.GetOutput("vpcId");

// In tests, vpcId will resolve to "vpc-12345678" based on the mock above
```

<!-- /option -->

<!-- option: java -->

```java
import java.util.List;

class MyMocks implements Mocks {
    @Override
    public CompletableFuture<ResourceResult> newResourceAsync(ResourceArgs args) {
        var state = new HashMap<>(args.inputs);
        // Handle StackReference resources
        if ("pulumi:pulumi:StackReference".equals(args.type)) {
            state.put("outputs", Map.of(
                "vpcId", "vpc-12345678",
                "subnetIds", List.of("subnet-11111111", "subnet-22222222"),
                "clusterName", "my-cluster"
            ));
        }
        return CompletableFuture.completedFuture(
            ResourceResult.of(Optional.of(args.name + "_id"), state)
        );
    }

    @Override
    public CompletableFuture<Map<String, Object>> callAsync(CallArgs args) {
        return CompletableFuture.completedFuture(Map.of());
    }
}
```

In your program, you can use a `StackReference` as usual:

```java
var networkStack = new StackReference("organization/network/prod",
    StackReferenceArgs.builder().build());
var vpcId = networkStack.getOutput(Output.of("vpcId"));

// In tests, vpcId will resolve to "vpc-12345678" based on the mock above
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support stack references in unit test mocks. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test programs that use stack references.

<!-- /option -->
<!-- /chooser -->

This approach lets you test how your program uses outputs from other stacks without needing those stacks to actually exist. You can mock different scenarios by returning different outputs in your test setup.

## Write the tests

<!-- chooser: language -->
<!-- option: typescript -->

The overall structure and scaffolding of our tests will look like any ordinary Mocha testing:

ec2tests.ts:

```typescript
import * as pulumi from "@pulumi/pulumi";
import "mocha";

pulumi.runtime.setMocks({
    // ... mocks as shown above
});

describe("Infrastructure", function() {
    let infra: typeof import("./index");

    before(async function() {
        // It's important to import the program _after_ the mocks are defined.
        infra = await import("./index");
    })

    describe("#server", function() {
        // TODO(check 1): Instances have a Name tag.
        // TODO(check 2): Instances must not use an inline userData script.
    });

    describe("#group", function() {
        // TODO(check 3): Instances must not have SSH open to the Internet.
    });
});
```

<!-- /option -->

<!-- option: python -->

The overall structure and scaffolding of our tests will look like any ordinary Python's unittest testing:

test_ec2.py:

```python
import unittest
import pulumi

# ... MyMocks as shown above
pulumi.runtime.set_mocks(MyMocks())

# It's important to import `infra` _after_ the mocks are defined.
import infra

class TestingWithMocks(unittest.TestCase):
    # TODO(check 1): Instances have a Name tag.
    # TODO(check 2): Instances must not use an inline userData script.
    # TODO(check 3): Instances must not have SSH open to the Internet.
```

<!-- /option -->

<!-- option: go -->

The overall structure and scaffolding of our tests will look like any ordinary Go test:

main_test.go:

```go
package main

import (
	"sync"
	"testing"

	"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
	"github.com/stretchr/testify/assert"
)

// ... mocks as shown above

func TestInfrastructure(t *testing.T) {
	err := pulumi.RunErr(func(ctx *pulumi.Context) error {
		infra, err := createInfrastructure(ctx)
		assert.NoError(t, err)

		var wg sync.WaitGroup
		wg.Add(3)

		// TODO(check 1): Instances have a Name tag.
		// TODO(check 2): Instances must not use an inline userData script.
		// TODO(check 3): Instances must not have SSH open to the Internet.

		wg.Wait()
		return nil
	}, pulumi.WithMocks("project", "stack", mocks(0)))
	assert.NoError(t, err)
}
```

<!-- /option -->

<!-- option: csharp -->

The overall structure and scaffolding of our tests will look like any ordinary NUnit testing:

WebserverStackTests.cs:

```csharp
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using NUnit.Framework;
using Pulumi.Aws.Ec2;

namespace UnitTesting
{
	  [TestFixture]
	  public class WebserverStackTests
	  {
        // TODO(check 1): Instances have a Name tag.
        // TODO(check 2): Instances must not use an inline userData script.
        // TODO(check 3): Instances must not have SSH open to the Internet.
	  }
}
```

<!-- /option -->

<!-- option: java -->

The overall structure and scaffolding of our tests will look like any ordinary JUnit 5 test class. Note that `PulumiTest.cleanup()` must be called after each test to reset the Pulumi runtime state:

Ec2Tests.java:

```java
package myproject;

import com.pulumi.test.PulumiTest;
import com.pulumi.test.TestOptions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

class Ec2Tests {
    @AfterEach
    void cleanup() {
        PulumiTest.cleanup();
    }

    // TODO(check 1): Instances have a Name tag.
    // TODO(check 2): Instances must not use an inline userData script.
    // TODO(check 3): Instances must not have SSH open to the Internet.
}
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

Now let's implement our first test: ensuring that instances have a `Name` tag. To verify this we need to grab hold of the EC2 instance object, and check the relevant property:

<!-- chooser: language -->
<!-- option: typescript -->

```typescript
// check 1: Instances have a Name tag.
it("must have a name tag", function(done) {
    pulumi.all([infra.server.urn, infra.server.tags]).apply(([urn, tags]) => {
        if (!tags || !tags["Name"]) {
            done(new Error(`Missing a name tag on server ${urn}`));
        } else {
            done();
        }
    });
});
```

<!-- /option -->

<!-- option: python -->

```python
# check 1: Instances have a Name tag.
@pulumi.runtime.test
def test_server_tags(self):
    def check_tags(args):
        urn, tags = args
        self.assertIsNotNone(tags, f'server {urn} must have tags')
        self.assertIn('Name', tags, 'server {urn} must have a name tag')

    return pulumi.Output.all(infra.server.urn, infra.server.tags).apply(check_tags)
```

<!-- /option -->

<!-- option: go -->

```go
// check 1: Instances have a Name tag.
pulumi.All(infra.server.URN(), infra.server.Tags).ApplyT(func(all []interface{}) error {
	urn := all[0].(pulumi.URN)
	tags := all[1].(map[string]string)

	assert.Containsf(t, tags, "Name", "missing a Name tag on server %v", urn)
	wg.Done()
	return nil
})
```

<!-- /option -->

<!-- option: csharp -->

```csharp
// check 1: Instances have a Name tag.
[Test]
public async Task InstanceHasNameTag()
{
    var resources = await Testing.RunAsync<WebserverStack>();

    var instance = resources.OfType<Instance>().FirstOrDefault();
    instance.Should().NotBeNull("EC2 Instance not found");

    var tags = await instance.Tags.GetValueAsync();
    tags.Should().NotBeNull("Tags are not defined");
    tags.Should().ContainKey("Name");
}
```

<!-- /option -->

<!-- option: java -->

```java
// check 1: Instances have a Name tag.
@Test
void instanceMustHaveNameTag() {
    var result = PulumiTest
        .withMocks(new MyMocks())
        .withOptions(TestOptions.builder()
            .projectName("project").stackName("stack").preview(false)
            .build())
        .runTest(App::stack);

    var instances = result.resources().stream()
        .filter(r -> r instanceof Instance)
        .map(r -> (Instance) r)
        .toList();

    assertFalse(instances.isEmpty(), "EC2 Instance not found");
    for (var instance : instances) {
        var urn = PulumiTest.extractValue(instance.urn());
        var tags = PulumiTest.extractValue(instance.tags());
        assertNotNull(tags, "Server " + urn + " must have tags");
        assertTrue(tags.containsKey("Name"), "Server " + urn + " must have a Name tag");
    }
}
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

This looks like a normal test, with a few noteworthy pieces:

- Since we're querying resource state without doing a deployment, there are many properties whose values will be undefined. This includes any output properties computed by your cloud provider that you did not explicitly return from the mocks. That's fine for these tests—we're checking for valid inputs anyway.
- Because all Pulumi resource properties are [outputs](/docs/concepts/inputs-outputs/)—since many of them are computed asynchronously—we need to use the `apply` method to get access to the values (see the `GetValueAsync` function in the `Testing.cs` file).
- Finally, since these outputs are resolved asynchronously, we need to use the framework's built-in asynchronous test capability.

After we've gotten through that setup, we get access to the raw inputs as plain values. The tags property is a map, so we make sure it is (1) defined, and (2) not missing an entry for the `Name` key. This is very basic, but we can check anything!

Now let's write our second check to assert that `userdata` property is empty:

<!-- chooser: language -->
<!-- option: typescript -->

```typescript
// check 2: Instances must not use an inline userData script.
it("must not use userData (use an AMI instead)", function(done) {
    pulumi.all([infra.server.urn, infra.server.userData]).apply(([urn, userData]) => {
        if (userData) {
            done(new Error(`Illegal use of userData on server ${urn}`));
        } else {
            done();
        }
    });
});
```

<!-- /option -->

<!-- option: python -->

```python
# check 2: Instances must not use an inline userData script.
@pulumi.runtime.test
def test_server_userdata(self):
    def check_user_data(args):
        urn, user_data = args
        self.assertFalse(user_data, f'illegal use of user_data on server {urn}')

    return pulumi.Output.all(infra.server.urn, infra.server.user_data).apply(check_user_data)
```

<!-- /option -->

<!-- option: go -->

```go
// check 2: Instances must not use an inline userData script.
pulumi.All(infra.server.URN(), infra.server.UserData).ApplyT(func(all []interface{}) error {
	urn := all[0].(pulumi.URN)
	userData := all[1].(string)

	assert.Emptyf(t, userData, "illegal use of userData on server %v", urn)
	wg.Done()
	return nil
})
```

<!-- /option -->

<!-- option: csharp -->

```csharp
// check 2: Instances must not use an inline userData script.
[Test]
public async Task InstanceMustNotUseInlineUserData()
{
    var resources = await Testing.RunAsync<WebserverStack>();

    var instance = resources.OfType<Instance>().FirstOrDefault();
    instance.Should().NotBeNull("EC2 Instance not found");

    var tags = await instance.UserData.GetValueAsync();
    tags.Should().BeNull();
}
```

<!-- /option -->

<!-- option: java -->

```java
// check 2: Instances must not use an inline userData script.
@Test
void instanceMustNotUseInlineUserData() {
    var result = PulumiTest
        .withMocks(new MyMocks())
        .withOptions(TestOptions.builder()
            .projectName("project").stackName("stack").preview(false)
            .build())
        .runTest(App::stack);

    var instance = result.resources().stream()
        .filter(r -> r instanceof Instance)
        .map(r -> (Instance) r)
        .findFirst().orElse(null);

    assertNotNull(instance, "EC2 Instance not found");
    var urn = PulumiTest.extractValue(instance.urn());
    var userData = PulumiTest.extractValue(instance.userData());
    assertNull(userData, "Illegal use of userData on server " + urn);
}
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

And finally, let's write our third check. It’s a bit more complex because we're searching for ingress rules associated with a security group—of which there may be many—and CIDR blocks within those ingress rules—of which there may also be many. But it's still several lines of code:

<!-- chooser: language -->
<!-- option: typescript -->

```typescript
// check 3: Instances must not have SSH open to the Internet.
it("must not open port 22 (SSH) to the Internet", function(done) {
    pulumi.all([infra.group.urn, infra.group.ingress]).apply(([urn, ingress ]) => {
        if (ingress.find(rule =>
            rule.fromPort === 22 && (rule.cidrBlocks || []).find(block => block === "0.0.0.0/0"))) {
                done(new Error(`Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group ${urn}`));
        } else {
            done();
        }
    });
});
```

<!-- /option -->

<!-- option: python -->

```python
# check 3: Test if port 22 for ssh is exposed.
@pulumi.runtime.test
def test_security_group_rules(self):
    def check_security_group_rules(args):
        urn, ingress = args
        ssh_open = any([rule['from_port'] == 22 and any([block == "0.0.0.0/0" for block in rule['cidr_blocks']]) for rule in ingress])
        self.assertFalse(ssh_open, f'security group {urn} exposes port 22 to the Internet (CIDR 0.0.0.0/0)')

    return pulumi.Output.all(infra.group.urn, infra.group.ingress).apply(check_security_group_rules)
```

<!-- /option -->

<!-- option: go -->

```go
// check 3: Test if port 22 for ssh is exposed.
pulumi.All(infra.group.URN(), infra.group.Ingress).ApplyT(func(all []interface{}) error {
	urn := all[0].(pulumi.URN)
	ingress := all[1].([]ec2.SecurityGroupIngress)

	for _, i := range ingress {
		openToInternet := false
		for _, b := range i.CidrBlocks {
			if b == "0.0.0.0/0" {
				openToInternet = true
				break
			}
		}

		assert.Falsef(t, i.FromPort == 22 && openToInternet, "illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group %v", urn)
	}

	wg.Done()
	return nil
})
```

<!-- /option -->

<!-- option: csharp -->

```csharp
// check 3: Test if port 22 for ssh is exposed.
[Test]
public async Task SecurityGroupMustNotHaveSshPortsOpenToInternet()
{
    var resources = await Testing.RunAsync<WebserverStack>();

    foreach (var securityGroup in resources.OfType<SecurityGroup>())
    {
        var urn = await securityGroup.Urn.GetValueAsync();
        var ingress = await securityGroup.Ingress.GetValueAsync();
        foreach (var rule in ingress)
        {
            (rule.FromPort == 22 && rule.CidrBlocks.Any(b => b == "0.0.0.0/0"))
                .Should().BeFalse($"Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group {urn}");
        }
    }
}
```

<!-- /option -->

<!-- option: java -->

```java
// check 3: Instances must not have SSH open to the Internet.
@Test
void securityGroupMustNotHaveSshOpenToInternet() {
    var result = PulumiTest
        .withMocks(new MyMocks())
        .withOptions(TestOptions.builder()
            .projectName("project").stackName("stack").preview(false)
            .build())
        .runTest(App::stack);

    for (var resource : result.resources()) {
        if (resource instanceof SecurityGroup group) {
            var urn = PulumiTest.extractValue(group.urn());
            var ingress = PulumiTest.extractValue(group.ingress());
            if (ingress != null) {
                for (var rule : ingress) {
                    var fromPort = PulumiTest.extractValue(rule.fromPort());
                    var cidrBlocks = PulumiTest.extractValue(rule.cidrBlocks());
                    boolean sshOpen = fromPort != null && fromPort == 22
                        && cidrBlocks != null && cidrBlocks.contains("0.0.0.0/0");
                    assertFalse(sshOpen, "Illegal SSH port 22 open to the Internet "
                        + "(CIDR 0.0.0.0/0) on group " + urn);
                }
            }
        }
    }
}
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

That's it—now let's run the tests.

## Run the tests

<!-- chooser: language -->
<!-- option: typescript -->

The command line to run your Mocha tests would therefore be:

```bash
$ mocha -r ts-node/register ec2tests.ts
```

<!-- /option -->

<!-- option: python -->

Run the following command to execute your Python tests:

```bash
$ python -m unittest
```

<!-- /option -->

<!-- option: go -->

Run the following command to execute your Go tests:

```bash
$ go test
```

<!-- /option -->

<!-- option: csharp -->

Run the following command to execute your Python tests:

```bash
$ dotnet test
```

<!-- /option -->

<!-- option: java -->

Run the following command to execute your Java tests:

```bash
$ mvn test
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

Running this will tell us that we have three failing tests, as we had planned.

<!-- chooser: language -->
<!-- option: typescript -->

```bash
  Infrastructure
    #server
      1) must have a name tag
      2) must not use userData (use an AMI instead)
    #group
      3) must not open port 22 (SSH) to the Internet

  0 passing (454ms)
  3 failing
```

<!-- /option -->

<!-- option: python -->

```bash
======================================================================
FAIL: test_security_group_rules (test_ec2.TestingWithMocks)
----------------------------------------------------------------------
...
======================================================================
FAIL: test_server_tags (test_ec2.TestingWithMocks)
----------------------------------------------------------------------
...
======================================================================
FAIL: test_server_userdata (test_ec2.TestingWithMocks)
----------------------------------------------------------------------
...
----------------------------------------------------------------------
Ran 3 tests in 0.034s

FAILED (failures=3)
```

<!-- /option -->

<!-- option: go -->

```bash
--- FAIL: TestInfrastructure (0.00s)
...
        	Error:      	Should be false
        	Test:       	TestInfrastructure
        	Messages:   	illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group urn:pulumi:stack::project::aws:ec2/securityGroup:SecurityGroup::web-secgrp
...
        	Error:      	Expected nil, but got: (*string)(0xc000217390)
        	Test:       	TestInfrastructure
        	Messages:   	illegal use of userData on server urn:pulumi:stack::project::aws:ec2/instance:Instance::web-server-www
...
        	Error:      	"map[]" does not contain "Name"
        	Test:       	TestInfrastructure
        	Messages:   	missing a Name tag on server urn:pulumi:stack::project::aws:ec2/instance:Instance::web-server-www
FAIL	testing-unit-go	0.501s
```

<!-- /option -->

<!-- option: csharp -->

```bash
X InstanceHasNameTag [387ms]
  Error Message:
   Expected tags not to be <null> because Tags are not defined.

X InstanceMustNotUseInlineUserData [17ms]
  Error Message:
   Expected tags to be <null>, but found "#!/bin/bash echo "Hello, World!" > index.html nohup python3 -m http.server 80 &".

X SecurityGroupMustNotHaveSshPortsOpenToInternet [11ms]
  Error Message:
   Expected boolean to be false because Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group urn:pulumi:stack::project::pulumi:pulumi:Stack$aws:ec2/securityGroup:SecurityGroup::web-secgrp, but found True.

Test Run Failed.
Total tests: 3
     Failed: 3
```

<!-- /option -->

<!-- option: java -->

```
[ERROR] Tests run: 3, Failures: 3, Errors: 0, Skipped: 0
[ERROR] Ec2Tests.instanceMustHaveNameTag -- AssertionFailedError: Server ... must have a Name tag
[ERROR] Ec2Tests.instanceMustNotUseInlineUserData -- AssertionFailedError: Illegal use of userData on server ...
[ERROR] Ec2Tests.securityGroupMustNotHaveSshOpenToInternet -- AssertionFailedError: Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group ...
[ERROR] BUILD FAILURE
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

Let's fix our program to comply:

<!-- chooser: language -->
<!-- option: typescript -->

index.ts:

```typescript
import * as aws from "@pulumi/aws";

export const group = new aws.ec2.SecurityGroup("web-secgrp", {
    ingress: [{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
    ],
});

export const server = new aws.ec2.Instance("web-server-www", {
    instanceType: "t2.micro",
    securityGroups: [group.name ], // reference the group object above
    ami: "ami-c55673a0",            // AMI for us-east-2 (Ohio)
    tags: { Name: "www-server" },   // name tag
});
```

<!-- /option -->

<!-- option: python -->

infra.py:

```python
import pulumi
from pulumi_aws import ec2

group = ec2.SecurityGroup('web-secgrp', ingress=[{ "protocol": "tcp", "from_port": 80, "to_port": 80, "cidr_blocks": ["0.0.0.0/0"] },
])

server = ec2.Instance('web-server-www;',
    instance_type="t2.micro",
    security_groups=[group.name ], # reference the group object above
    tags={'Name': 'webserver'},     # name tag
    ami="ami-c55673a0")             # AMI for us-east-2 (Ohio)
```

<!-- /option -->

<!-- option: go -->

main.go:

```go
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

type infrastructure struct {
	group  *ec2.SecurityGroup
	server *ec2.Instance
}

func createInfrastructure(ctx *pulumi.Context) (*infrastructure, error) {
	group, err := ec2.NewSecurityGroup(ctx, "web-secgrp", &ec2.SecurityGroupArgs{
		Ingress: ec2.SecurityGroupIngressArray{
			ec2.SecurityGroupIngressArgs{
				Protocol:   pulumi.String("tcp"),
				FromPort:   pulumi.Int(80),
				ToPort:     pulumi.Int(80),
				CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")},
			},
		},
	})
	if err != nil {
		return nil, err
	}

	server, err := ec2.NewInstance(ctx, "web-server-www", &ec2.InstanceArgs{
		InstanceType:   pulumi.String("t2-micro"),
		SecurityGroups: pulumi.StringArray{group.ID()}, // reference the group object above
		Ami:            pulumi.String("ami-c55673a0"),  // AMI for us-east-2 (Ohio)
		Tags:           pulumi.StringMap{"Name": pulumi.String("webserver")},
	})
	if err != nil {
		return nil, err
	}

	return &infrastructure{
		group:  group,
		server: server,
	}, nil
}
```

<!-- /option -->

<!-- option: csharp -->

WebserverStack.cs:

```csharp
using Pulumi;
using Pulumi.Aws.Ec2;
using Pulumi.Aws.Ec2.Inputs;

public class WebserverStack : Stack
{
    public WebserverStack()
    {
        var group = new SecurityGroup("web-secgrp", new SecurityGroupArgs
        {
            Ingress =
            {
                new SecurityGroupIngressArgs { Protocol = "tcp", FromPort = 80, ToPort = 80, CidrBlocks = { "0.0.0.0/0" } }
            }
        });

        var server = new Instance("web-server-www", new InstanceArgs
        {
            InstanceType = "t2.micro",
            SecurityGroups = { group.Name }, // reference the group object above
            Ami = "ami-c55673a0",            // AMI for us-east-2 (Ohio)
            Tags = { { "Name", "webserver" }}// name tag
        });
    }
}
```

<!-- /option -->

<!-- option: java -->

App.java:

```java
package myproject;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.aws.ec2.Instance;
import com.pulumi.aws.ec2.InstanceArgs;
import com.pulumi.aws.ec2.SecurityGroup;
import com.pulumi.aws.ec2.SecurityGroupArgs;
import com.pulumi.aws.ec2.inputs.SecurityGroupIngressArgs;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var group = new SecurityGroup("web-secgrp", SecurityGroupArgs.builder()
            .ingress(
                SecurityGroupIngressArgs.builder()
                    .protocol("tcp").fromPort(80).toPort(80).cidrBlocks("0.0.0.0/0")
                    .build())
            .build());

        var server = new Instance("web-server-www", InstanceArgs.builder()
            .instanceType("t2.micro")
            .securityGroups(group.name())  // reference the group object above
            .ami("ami-c55673a0")           // AMI for us-east-2 (Ohio)
            .tags(Map.of("Name", "webserver")) // name tag
            .build());
    }
}
```

<!-- /option -->

<!-- option: yaml -->

Pulumi.yaml:

```yaml
name: webserver
runtime: yaml
resources:
  web-secgrp:
    type: aws:ec2:SecurityGroup
    properties:
      ingress:
        - protocol: tcp
          fromPort: 80
          toPort: 80
          cidrBlocks: ["0.0.0.0/0"]
  web-server-www:
    type: aws:ec2:Instance
    properties:
      instanceType: t2.micro
      securityGroups:
        - ${web-secgrp.name}
      ami: ami-c55673a0
      tags:
        Name: webserver
```

<!-- /option -->
<!-- /chooser -->

And then rerun our tests:

<!-- chooser: language -->
<!-- option: typescript -->

```
Infrastructure
    #server
      ✓ must have a name tag
      ✓ must not use userData (use an AMI instead)
    #group
      ✓ must not open port 22 (SSH) to the Internet

  3 passing (454ms)
```

<!-- /option -->

<!-- option: python -->

```
----------------------------------------------------------------------
Ran 3 tests in 0.022s

OK
```

<!-- /option -->

<!-- option: go -->

```
PASS
ok  	testing-unit-go	0.704s
```

<!-- /option -->

<!-- option: csharp -->

```
Test Run Successful.
Total tests: 3
     Passed: 3
```

<!-- /option -->

<!-- option: java -->

```
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
```

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

<!-- /option -->
<!-- /chooser -->

All the tests passed!

## Limitations

When using mocks for unit testing, it's important to understand that the mock server does not implement the full Pulumi engine. This means certain features that rely on the engine's deployment orchestration will not execute during mock-based tests.

### Lifecycle hooks and transforms

Lifecycle hooks and resource transforms are not executed in mock tests. While your program can register hooks and transforms with the mock server, they will not actually run during test execution.

This limitation exists because implementing full hook and transform support would require reimplementing significant portions of the Pulumi engine in each language SDK. Since mocks are designed to run fast and deterministically without external dependencies, this trade-off is intentional.

**How to handle this in tests:**

If your program uses lifecycle hooks or transforms, structure your tests to work around this limitation:

1. **Test the logic separately**: Extract the logic from hooks and transforms into standalone functions that can be unit tested independently.
1. **Mock the expected outcomes**: Configure your mocks to return resource state that reflects what would happen after hooks or transforms execute.
1. **Use integration tests**: For end-to-end validation of hook and transform behavior, use integration tests that deploy actual resources to a testing environment.

For example, if you have a transform that adds default tags to all resources, your mock's `newResource` function can return resource state that already includes those tags, simulating the transform's effect without actually executing it.

## Full example

<!-- chooser: language -->
<!-- option: typescript -->

The full code for this guide is available in the examples repository: [Unit Tests in TypeScript](https://github.com/pulumi/examples/tree/master/testing-unit-ts).

 

<!-- /option -->

<!-- option: python -->

The full code for this guide is available in the examples repository: [Unit Tests in Python](https://github.com/pulumi/examples/tree/master/testing-unit-py).

 

<!-- /option -->

<!-- option: go -->

The full code for this guide is available in the examples repository: [Unit Tests in Go](https://github.com/pulumi/examples/tree/master/testing-unit-go).

 

<!-- /option -->

<!-- option: csharp -->

The full code for this guide is available in the examples repository: [Unit Tests in C#](https://github.com/pulumi/examples/tree/master/testing-unit-cs).

 

<!-- /option -->

<!-- option: java -->

A Java unit testing example is not yet available in the examples repository. Contributions are welcome at [pulumi/examples](https://github.com/pulumi/examples).

 

<!-- /option -->

<!-- option: yaml -->

YAML programs are declarative and do not support mock-based unit testing. See [integration testing](/docs/iac/guides/testing/integration/) to learn how to test your Pulumi YAML programs.

 

<!-- /option -->
<!-- /chooser -->


