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.
The build-in testing support in Pulumi has the following pain points:
- to add a mock, we have to figure out the string representations of the resource identifiers
- the mock replaces the property as a whole, potentially removing child properties that are needed for the test
- mocking stack reference is not straightforward
- a lot of setup code is needed to create a mock
- 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.
Two build methods are provided:
publicasyncTask<StackResult>BuildStackAsync<T>(TestOptions?testOptions=null)whereT:Stack,new()publicasyncTask<StackResult>BuildStackAsync<T>(IServiceProviderserviceProvider,TestOptions?testOptions=null)whereT:Stack,new()var(resources,resourceInputs)=awaitnewStackBuilder().BuildStackAsync<MyStack>();var(resources,resourceInputs)=awaitnewStackBuilder().BuildStackAsync<MyStack>(serviceProvider);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.
publicStackBuilderAddMocksForAllResources(Func<MockResourceArgs,Dictionary<string,object>>mocks)Below tests shows that arn property of all resources is mocked with the resource name appended with _arn.
[Fact]publicasyncTaskShould_Mock_All_Resource_Properties_With_Given_Rule(){varresult=awaitnewStackBuilder().BuildStackAsync<AwsStack>();varrepository=result.Resources.OfType<Repository>().Single();repository.Arn.GetValue().Should().BeNull();result=awaitnewStackBuilder().AddMocksForAllResources(args =>newDictionary<string,object>{{"arn",$"{args.Name}_arn"}}).BuildStackAsync<AwsStack>();varresources=result.Resources;repository=resources.OfType<Repository>().Single();repository.Arn.GetValue().Should().Be("my-repository_arn");varbucket=resources.OfType<Bucket>().Single();bucket.Arn.GetValue().Should().Be("my-bucket_arn");}Four methods are provided to add mocks for resources:
AddResourceMock(ResourceMockresourceMock)
AddResourceMocks(List<ResourceMock>resourceMocks)
AddResourceMockFunc(ResourceMockFuncresourceMockFunc)
AddResourceMockFuncs(List<ResourceMockFunc>resourceMockFuncs)varresult=awaitnewStackBuilder().AddResourceMock(newResourceMock(typeof(Image),newDictionary<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.
varresult=awaitnewStackBuilder().AddResourceMockFunc(newResourceMockFunc(typeof(Image),
args =>newDictionary<string,object>{{"imageUri",$"{args.Name}-{imageUri}"}})).BuildStackAsync<MyStack>();Three methods are provided to add mocks for calls:
AddCallMock(CallMockcallMock)
AddCallMocks(List<CallMock>callMocks)
AddCallMockFunc(CallMockFunccallMockFunc)Below code makes a call to GetRepository and assigns the RepositoryUrl value to the RepositoryUrl property of the stack as stack output.
[Output]publicOutput<string>RepositoryUrl{get;set;}
public MyStack(){varinvoke=GetRepository.Invoke(newGetRepositoryInvokeArgs{Name="my-remote-repository"});RepositoryUrl=invoke.Apply(x =>x.RepositoryUrl);}To test the above:
varresult=awaitnewStackBuilder().AddCallMock(newCallMock(typeof(GetRepository),newDictionary<string,object>{{"repositoryUrl",mock}})).BuildStackAsync<MyStack>();varstack=result.Resources.OfType<MyStack>().Single();stack.RepositoryUrl.GetValue().Should().Be(mock);To mock stack references, use AddStackReferenceMock method.
Below code uses a stack reference to get the hosted-zone-id output from another stack.
publicclassCoreStackReference{publicreadonlyOutput<string>HostedZoneId;publicCoreStackReference(){varcoreStackReference=newStackReference("core");HostedZoneId=coreStackReference.RequireOutput("hosted-zone-id").Apply(x =>x.ToString())!;}}varcoreStackReference=newCoreStackReference();_=newBucket("my-bucket",newBucketArgs{BucketName="my-bucket",HostedZoneId=coreStackReference.HostedZoneId});To test the above:
[Fact]publicasyncTaskShould_Add_StackReference_Mock(){varnoStackReferenceMock=()=>newStackBuilder().BuildStackAsync<AwsStack>();awaitnoStackReferenceMock.Should().ThrowAsync<Exception>().WithMessage("*Required output 'hosted-zone-id' does not exist on stack 'core'*");varresult=awaitnewStackBuilder().AddStackReferenceMock(newStackReferenceMock("core",newDictionary<string,object>{{"hosted-zone-id","hosted-zone-id-mock"}})).BuildStackAsync<AwsStack>();varbucket=result.Resources.OfType<Bucket>().Single();bucket.HostedZoneId.GetValue().Should().Be("hosted-zone-id-mock");}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:
varresult=await_baseStackBuilder.AddResourceMock(newResourceMock(typeof(Repository),newDictionary<string,object>{{"repositoryUrl","my-repository_name"}})).BuildStackAsync<AwsStack>();varinputs=result.ResourceInputs.GetInputs("my-image");varplatform=inputs.GetValueOrDefault("platform");platform.Should().Be("linux/amd64");varcontext=inputs.GetValueOrDefault("context");context.Should().Be("./");varrepositoryUrl=inputs.GetValueOrDefault("repositoryUrl");repositoryUrl.Should().Be("my-repository_name");Please feel free to contribute to this project. PRs are welcome.