PulumiTestHelper 0.1.0

dotnet add package PulumiTestHelper --version 0.1.0                
NuGet\Install-Package PulumiTestHelper -Version 0.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PulumiTestHelper" Version="0.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add PulumiTestHelper --version 0.1.0                
#r "nuget: PulumiTestHelper, 0.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install PulumiTestHelper as a Cake Addin
#addin nuget:?package=PulumiTestHelper&version=0.1.0

// Install PulumiTestHelper as a Cake Tool
#tool nuget:?package=PulumiTestHelper&version=0.1.0                

Pulumi Test Helper

This is a helper package for testing Pulumi programs.

The goal of this package is to make testing feel like first-class citizen in Pulumi.

Motivation

The build-in testing support in Pulumi has the following pain points:

  1. to add a mock, we have to figure out the string representations of the resource identifiers
  2. the mock replaces the property as a whole, potentially removing child properties that are needed for the test
  3. mocking stack reference is not straightforward
  4. a lot of setup code is needed to create a mock
  5. only outputs of resources can be tested

This package aims to solve these problems by providing a simple and easy to use API for mocking resources, calls and stack references.

It also provide a way to test the raw inputs of resources.

Usage

Build the stack

Two build methods are provided:

public async Task<StackResult> BuildStackAsync<T>(TestOptions? testOptions = null) where T : Stack, new()
    
public async Task<StackResult> BuildStackAsync<T>(IServiceProvider serviceProvider,
        TestOptions? testOptions = null) where T : Stack, new()
Example:
var (resources, resourceInputs) = await new StackBuilder().BuildStackAsync<MyStack>();
var (resources, resourceInputs) = await new StackBuilder().BuildStackAsync<MyStack>(serviceProvider);

Add Mocks for all resources

AddMocksForAllResources is provided to add mocks for all resources in the stack.

It takes MockResourceArgs as input and returns a dictionary of the resource properties, so that you can provide mocks based on existing resource inputs.

public StackBuilder AddMocksForAllResources(Func<MockResourceArgs, Dictionary<string, object>> mocks)
Example

Below tests shows that arn property of all resources is mocked with the resource name appended with _arn.

[Fact]
public async Task Should_Mock_All_Resource_Properties_With_Given_Rule()
{
    var result = await new StackBuilder().BuildStackAsync<AwsStack>();

    var repository = result.Resources.OfType<Repository>().Single();
    repository.Arn.GetValue().Should().BeNull();

    result = await new StackBuilder().AddMocksForAllResources(args => new Dictionary<string, object>
    {
        {"arn", $"{args.Name}_arn"}
    }).BuildStackAsync<AwsStack>();

    var resources = result.Resources;

    repository = resources.OfType<Repository>().Single();
    repository.Arn.GetValue().Should().Be("my-repository_arn");

    var bucket = resources.OfType<Bucket>().Single();
    bucket.Arn.GetValue().Should().Be("my-bucket_arn");
}

Mock resources

Four methods are provided to add mocks for resources:

AddResourceMock(ResourceMock resourceMock)
AddResourceMocks(List<ResourceMock> resourceMocks)
AddResourceMockFunc(ResourceMockFunc resourceMockFunc)
AddResourceMockFuncs(List<ResourceMockFunc> resourceMockFuncs)
Example:
var result = await new StackBuilder()
        .AddResourceMock(new ResourceMock(typeof(Image),
            new Dictionary<string, object>
            {
                {"imageUri", imageUri}
            }))
        .BuildStackAsync<MyStack>();

image = result.Resources.OfType<Image>().Single(x => x.HasName("my-image"));
image.ImageUri.GetValue().Should().Be(imageUri);

AddResourceMockFunc takes the pulumi MockResourceArgs as input and returns a dictionary of the resource properties, so that you can provide mocks based on existing resource inputs.

var result = await new StackBuilder()
    .AddResourceMockFunc(
        new ResourceMockFunc(typeof(Image),
                    args => new Dictionary<string, object>
                    {
                        {"imageUri", $"{args.Name}-{imageUri}"}
                    })
        )
        .BuildStackAsync<MyStack>();

Mock calls

Three methods are provided to add mocks for calls:

AddCallMock(CallMock callMock)
AddCallMocks(List<CallMock> callMocks)
AddCallMockFunc(CallMockFunc callMockFunc)
Example:

Below code makes a call to GetRepository and assigns the RepositoryUrl value to the RepositoryUrl property of the stack as stack output.

[Output] public Output<string> RepositoryUrl { get; set; }
public MyStack()
{
    var invoke = GetRepository.Invoke(new GetRepositoryInvokeArgs
    {
        Name = "my-remote-repository"
    });

    RepositoryUrl = invoke.Apply(x => x.RepositoryUrl);
}

To test the above:

var result = await new StackBuilder()
    .AddCallMock(new CallMock(typeof(GetRepository),
        new Dictionary<string, object>
        {
            {"repositoryUrl", mock}
        })).BuildStackAsync<MyStack>();
var stack = result.Resources.OfType<MyStack>().Single();
stack.RepositoryUrl.GetValue().Should().Be(mock);

Mock stack references

To mock stack references, use AddStackReferenceMock method.

Example:

Below code uses a stack reference to get the hosted-zone-id output from another stack.

public class CoreStackReference
{
    public readonly Output<string> HostedZoneId;

    public CoreStackReference()
    {
        var coreStackReference = new StackReference("core");
        HostedZoneId = coreStackReference.RequireOutput("hosted-zone-id").Apply(x => x.ToString())!;
    }
}
var coreStackReference = new CoreStackReference();

_ = new Bucket("my-bucket", new BucketArgs
{
    BucketName = "my-bucket",
    HostedZoneId = coreStackReference.HostedZoneId
});

To test the above:

[Fact]
public async Task Should_Add_StackReference_Mock()
{
    var noStackReferenceMock = () => new StackBuilder().BuildStackAsync<AwsStack>();
    await noStackReferenceMock.Should().ThrowAsync<Exception>()
        .WithMessage("*Required output 'hosted-zone-id' does not exist on stack 'core'*");

    var result = await new StackBuilder()
        .AddStackReferenceMock(new StackReferenceMock("core", new Dictionary<string, object>
        {
            {"hosted-zone-id", "hosted-zone-id-mock"}
        })).BuildStackAsync<AwsStack>();
    var bucket = result.Resources.OfType<Bucket>().Single();
    bucket.HostedZoneId.GetValue().Should().Be("hosted-zone-id-mock");
}

Test raw inputs

The resource list from Pulumi Deployment.TestAsync (which is used by BuildStackAsync) containers list of resources specified in the stack.

However, the properties of the resources only includes the Pulumi outputs of the resources, many of the raw properties are absent.

In case you want to protect against changes in the inputs of the resources, you can use ResourceInputs property of the StackResult to test the inputs of the resources.

For example, Awsx.Erc.Image resource only have imageUri output, it does not output other properties like Platform, Context etc.

var repository = new Repository("my-repository", new RepositoryArgs
        {
            ImageScanningConfiguration = new RepositoryImageScanningConfigurationArgs
            {
                ScanOnPush = false
            },
            ForceDelete = false,
            ImageTagMutability = "MUTABLE"
        });
        
new Image("my-image", new ImageArgs
{
    Platform = "linux/amd64",
    Context = "./",
    RepositoryUrl = repository.RepositoryUrl
});

To test the inputs of the resource Image:

var result = await _baseStackBuilder.AddResourceMock(new ResourceMock(typeof(Repository),
    new Dictionary<string, object>
    {
        {"repositoryUrl", "my-repository_name"}
    })).BuildStackAsync<AwsStack>();

var inputs = result.ResourceInputs.GetInputs("my-image");
var platform = inputs.GetValueOrDefault("platform");
platform.Should().Be("linux/amd64");
        
var context = inputs.GetValueOrDefault("context");
context.Should().Be("./");

var repositoryUrl = inputs.GetValueOrDefault("repositoryUrl");
repositoryUrl.Should().Be("my-repository_name");

Contribution

Please feel free to contribute to this project. PRs are welcome.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.1.0 148 2/4/2024