Skip to content

Commit

Permalink
Add examples of mocks to unit tests (pulumi#807)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhailshilkov committed Oct 5, 2020
1 parent 749bdf6 commit cb53931
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 31 deletions.
45 changes: 36 additions & 9 deletions testing-unit-cs/Testing.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
// Copyright 2016-2020, Pulumi Corporation

using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Moq;
using Pulumi;
using Pulumi.Testing;

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

// Forward all input parameters as resource outputs, so that we could test them.
outputs.AddRange(inputs);

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

// Default the resource ID to `{name}_id`.
// We could also format it as `/subscription/abc/resourceGroups/xyz/...` if that was important for tests.
id ??= $"{name}_id";
return Task.FromResult((id, (object)outputs));
}

public Task<object> CallAsync(string token, ImmutableDictionary<string, object> inputs, string? provider)
{
var outputs = ImmutableDictionary.CreateBuilder<string, object>();

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

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

/// <summary>
/// Helper methods to streamlines unit testing experience.
/// </summary>
Expand All @@ -19,13 +52,7 @@ public static class Testing
/// </summary>
public static Task<ImmutableArray<Resource>> RunAsync<T>() where T : Stack, new()
{
var mocks = new Mock<IMocks>();
mocks.Setup(m => m.NewResourceAsync(It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<ImmutableDictionary<string, object>>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((string type, string name, ImmutableDictionary<string, object> inputs, string? provider, string? id) => (name + "_id", inputs));
mocks.Setup(m => m.CallAsync(It.IsAny<string>(), It.IsAny<ImmutableDictionary<string, object>>(), It.IsAny<string>()))
.ReturnsAsync((string token, ImmutableDictionary<string, object> args, string? provider) => args);
return Deployment.TestAsync<T>(mocks.Object, new TestOptions { IsPreview = false });
return Deployment.TestAsync<T>(new Mocks(), new TestOptions { IsPreview = false });
}

/// <summary>
Expand Down
1 change: 0 additions & 1 deletion testing-unit-cs/UnitTesting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Pulumi.Aws" Version="3.*" />
Expand Down
19 changes: 17 additions & 2 deletions testing-unit-cs/WebserverStack.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright 2016-2020, Pulumi Corporation

using Pulumi;
using Pulumi.Aws;
using Pulumi.Aws.Ec2;
using Pulumi.Aws.Ec2.Inputs;
using Pulumi.Aws.Inputs;

/// <summary>
/// A simple stack to be tested.
Expand All @@ -21,13 +23,26 @@ public WebserverStack()
}
});

var amiId = Output.Create(GetAmi.InvokeAsync(new GetAmiArgs
{
MostRecent = true,
Owners = {"099720109477"},
Filters = {
new GetAmiFilterArgs
{
Name = "name",
Values = {"ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"},
}
}
})).Apply(ami => ami.Id);

// var userData = "#!/bin/bash echo \"Hello, World!\" > index.html nohup python -m SimpleHTTPServer 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),
VpcSecurityGroupIds = { group.Id }, // reference the group object above
Ami = amiId,
// Comment out to fail a test:
Tags = { { "Name", "webserver" }},
// Uncomment to fail a test:
Expand Down
24 changes: 20 additions & 4 deletions testing-unit-go/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"github.com/pulumi/pulumi-aws/sdk/v3/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v3/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)
Expand Down Expand Up @@ -32,14 +33,29 @@ func createInfrastructure(ctx *pulumi.Context) (*infrastructure, error) {
return nil, err
}

mostRecent := true
ami, err := aws.GetAmi(ctx, &aws.GetAmiArgs{
Filters: []aws.GetAmiFilter{
{
Name: "name",
Values: []string{"ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"},
},
},
Owners: []string{"137112412989"},
MostRecent: &mostRecent,
})
if err != nil {
return nil, err
}

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

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)
InstanceType: pulumi.String("t2-micro"),
VpcSecurityGroupIds: pulumi.StringArray{group.ID()}, // reference the group object above
Ami: pulumi.String(ami.Id),
// Comment out to fail a test:
Tags: pulumi.Map{"Name": pulumi.String("webserver")},
Tags: pulumi.StringMap{"Name": pulumi.String("webserver")},
// Uncomment to fail a test:
//UserData: pulumi.String(userData), // start a simple web server
})
Expand Down
18 changes: 14 additions & 4 deletions testing-unit-go/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@ import (
type mocks int

// Create the mock.
func (mocks) NewResource(typeToken, name string, inputs resource.PropertyMap, provider, id string) (string, resource.PropertyMap, error) {
return name + "_id", inputs, nil
func (mocks) NewResource(token, name string, inputs resource.PropertyMap, provider, id string) (string, resource.PropertyMap, error) {
outputs := inputs.Mappable()
if token == "aws:ec2/instance:Instance" {
outputs["publicIp"] = "203.0.113.12"
outputs["publicDns"] = "ec2-203-0-113-12.compute-1.amazonaws.com"
}
return name + "_id", resource.NewPropertyMapFromMap(outputs), nil
}

func (mocks) Call(token string, args resource.PropertyMap, provider string) (resource.PropertyMap, error) {
return args, nil
outputs := map[string]interface{}{}
if token == "aws:index/getAmi:getAmi" {
outputs["architecture"] = "x86_64"
outputs["id"] = "ami-0eb1f3cdeeb8eed2a"
}
return resource.NewPropertyMapFromMap(outputs), nil
}

// Applying unit tests.
Expand All @@ -33,7 +43,7 @@ func TestInfrastructure(t *testing.T) {
// Test if the service has tags and 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]interface{})
tags := all[1].(map[string]string)

assert.Containsf(t, tags, "Name", "missing a Name tag on server %v", urn)
wg.Done()
Expand Down
22 changes: 16 additions & 6 deletions testing-unit-py/infra.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pulumi
from pulumi_aws import ec2
from pulumi_aws import ec2, get_ami, GetAmiFilterArgs

group = ec2.SecurityGroup('web-secgrp', ingress=[
# Uncomment to fail a test:
Expand All @@ -9,11 +9,21 @@

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

server = ec2.Instance('web-server-www;',
ami_id = get_ami(
most_recent=True,
owners=["099720109477"],
filters=[
GetAmiFilterArgs(
name="name",
values=["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
)]
).id

server = ec2.Instance('web-server-www',
instance_type="t2.micro",
security_groups=[ group.name ], # reference the group object above
vpc_security_group_ids=[ group.id ], # reference the group object above
# Comment out to fail a test:
tags={'Name': 'webserver'}, # name tag
ami="ami-c55673a0") # AMI for us-east-2 (Ohio)
tags={'Name': 'webserver'}, # name tag
# Uncomment to fail a test:
#user_data=user_data) # start a simple web server
#user_data=user_data) # start a simple web server
ami=ami_id)
16 changes: 14 additions & 2 deletions testing-unit-py/test_ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
import pulumi

class MyMocks(pulumi.runtime.Mocks):
def new_resource(self, type_, name, inputs, provider, id_):
return [name + '_id', inputs]
def new_resource(self, token, name, inputs, provider, id_):
outputs = inputs
if token == "aws:ec2/instance:Instance":
outputs = {
**inputs,
"publicIp": "203.0.113.12",
"publicDns": "ec2-203-0-113-12.compute-1.amazonaws.com",
}
return [name + '_id', outputs]
def call(self, token, args, provider):
if token == "aws:index/getAmi:getAmi":
return {
"architecture": "x86_64",
"id": "ami-0eb1f3cdeeb8eed2a",
}
return {}

pulumi.runtime.set_mocks(MyMocks())
Expand Down
10 changes: 9 additions & 1 deletion testing-unit-ts/ec2tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ pulumi.runtime.setMocks({
}
},
call: function(token: string, args: any, provider?: string) {
return args;
switch (token) {
case "aws:index/getAmi:getAmi":
return {
"architecture": "x86_64",
"id": "ami-0eb1f3cdeeb8eed2a",
};
default:
return args;
}
},
});

Expand Down
13 changes: 11 additions & 2 deletions testing-unit-ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ export const group = new aws.ec2.SecurityGroup("web-secgrp", {
],
});

const amiId = aws.getAmi({
mostRecent: true,
owners: ["099720109477"],
filters: [{
name: "name",
values: ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"],
}],
}).then(ami => ami.id);

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),
vpcSecurityGroupIds: [ group.id ], // reference the group object above
ami: amiId,
// comment to fail a test:
tags: { Name: "www-server" }, // name tag
// uncomment to fail a test:
Expand Down

0 comments on commit cb53931

Please sign in to comment.