Skip to content

Instantly share code, notes, and snippets.

@mikhailshilkov
Last active January 17, 2020 02:31
Show Gist options
  • Save mikhailshilkov/e2f341d7d15174cea4d1041f5422d2ad to your computer and use it in GitHub Desktop.
Save mikhailshilkov/e2f341d7d15174cea4d1041f5422d2ad to your computer and use it in GitHub Desktop.
Mock-based unit tests examples in C#
class MyStack : Stack
{
public MyStack()
{
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 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),
UserData = userData, // start a simple web server
//Tags = { {"Name", "foo"} }
});
this.PublicIp = server.PublicIp;
this.PublicHostName = server.PublicDns;
}
[Output] public Output<string> PublicIp { get; set; }
[Output] public Output<string> PublicHostName { get; set; }
}
public class Tests
{
[Fact]
public async Task MustHaveNameTag()
{
var tags = await TestProperty<ImmutableDictionary<string, object>>("aws:ec2/instance:Instance", "tags");
Assert.True(tags != null, "Missing tags on an EC2 instance");
Assert.True(tags["Name"] != null, "Missing a 'Name' tag on an EC2 instance");
}
[Fact]
public async Task MustNotUseUserData()
{
var userData = await TestProperty<string>("aws:ec2/instance:Instance", "userData");
Assert.True(userData == null, "Illegal use of userData on an EC2 instance");
}
[Fact]
public async Task MustNameSecurityGroup()
{
var securityGroups = await TestProperty<ImmutableArray<object>>("aws:ec2/instance:Instance", "securityGroups");
Assert.NotNull(securityGroups);
Assert.True(securityGroups.Length > 0, "Expected at least one security group");
Assert.True(!string.IsNullOrEmpty((string?)securityGroups[0]), "Illegal security group spec");
}
[Fact]
public async Task InstancesMustNotHaveSshPortOpenToInternet()
{
var ingress = await TestProperty<ImmutableArray<object>>("aws:ec2/securityGroup:SecurityGroup", "ingress");
foreach (ImmutableDictionary<string, object> rule in ingress)
{
var fromPort = (double) rule["fromPort"];
var cidrBlocks = (ImmutableArray<object>) rule["cidrBlocks"];
foreach (string cidrBlock in cidrBlocks)
{
Assert.False(fromPort == 22 && cidrBlock == "0.0.0.0/0", $"Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0)");
}
}
}
private async Task<T> TestProperty<T>(string targetType, string propertyName)
{
var mock = new Mock<IMocks>();
// Catch an error if it occurs and fail the test if so
string? loggedError = null;
mock.Setup(m => m.HandleErrorAsync(It.IsAny<string>())).Callback((string v) => loggedError = v);
// Capture the value of the target property of the target type
T result = default;
mock.Setup(m => m.NewResourceAsync(It.IsAny<Resource>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<ImmutableDictionary<string, object>>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync((Resource resource, string type, string name, ImmutableDictionary<string, object> inputs, string provider,
string id) =>
{
if (type == targetType && inputs.TryGetValue(propertyName, out var value))
{
result = (T)value;
}
var outputs = inputs.Add("name", "test");
return (type + "-test", outputs);
});
var statusCode = await Deployment.TestAsync<MyStack>(mock.Object);
Assert.Null(loggedError);
Assert.Equal(0, statusCode);
return result;
}
}
public class OtherTests
{
[Fact]
public async Task MustHaveNameTag()
{
var tags = await TestProperty<Instance, ImmutableDictionary<string, object>>(i => i.Tags);
Assert.True(tags != null, "Missing tags on an EC2 instance");
Assert.True(tags["Name"] != null, "Missing a 'Name' tag on an EC2 instance");
}
[Fact]
public async Task MustNotUseUserData()
{
var userData = await TestProperty<Instance, string>(i => i.UserData);
Assert.True(userData == null, "Illegal use of userData on an EC2 instance");
}
[Fact]
public async Task MustNameSecurityGroup()
{
var securityGroups = await TestProperty<Instance, ImmutableArray<string>>(i => i.SecurityGroups);
Assert.NotNull(securityGroups);
Assert.True(securityGroups.Length > 0, "Expected at least one security group");
Assert.True(!string.IsNullOrEmpty(securityGroups[0]), "Illegal security group spec");
}
[Fact]
public async Task InstancesMustNotHaveSshPortOpenToInternet()
{
var ingress = await TestProperty<SecurityGroup, ImmutableArray<SecurityGroupIngress>>(sg => sg.Ingress);
foreach (var rule in ingress)
{
foreach (var cidrBlock in rule.CidrBlocks)
{
Assert.False(rule.FromPort == 22 && cidrBlock == "0.0.0.0/0",
"Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0)");
}
}
}
private async Task<V> TestProperty<T, V>(Func<T, Output<V>> getter)
{
var result = await Deployment.TestAsync<MyStack>();
Assert.False(result.HasErrors, "Expected the deployment to succeed");
var resource = result.Resources.OfType<T>().SingleOrDefault();
Assert.True(resource != null, $"Expected to find a single {typeof(T).Name}");
return await result.GetAsync(getter(resource));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment