Last active
January 17, 2020 02:31
-
-
Save mikhailshilkov/e2f341d7d15174cea4d1041f5422d2ad to your computer and use it in GitHub Desktop.
Mock-based unit tests examples in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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