如何使用 Azure Key Vault 的 Moq 进行单元测试
How to unit test with Moq the Azure Key Vault
我需要模拟 Key Vault 的端点,以便知道我是否正在调用函数来获取 Key Vault 一次。
我正在使用 C# 和 Moq(框架)开发它以进行测试。
界面如下:
public interface IKeyVaultConnection
{
string GetKeyVaultValue(string variableName);
}
public class KeyVaultConnection
{
public KeyVaultClient keyVaultClient;
private string endpointKeyVault;
public KeyVaultConnection(string keyVaultAddress = "DefaultEndpoint")
{
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
endpointKeyVault = $"https://{ keyVaultAddress }.vault.azure.net";
}
private async Task<string> AsyncGetSecretValue(string keyName)
{
var secret = await keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }")
.ConfigureAwait(false);
return secret.Value;
}
public string GetKeyVaultValue(string variableName)
{
Task<string> task = Task.Run(async () => await AsyncGetSecretValue(variableName));
task.Wait();
return task.Result;
}
}
Mock<IKeyVaultConnection> mock = new Mock<IKeyVaultConnection>();
//----->>>>>> Need to setup the endpoint
mock.Setup(x => x.GetKeyVaultValue(It.IsAny<string>())).Returns(It.IsAny<string>());
// mock.Verify(x => x.GetKeyVaultValue(It.IsAny<string>()), Times.Once());
我需要的是伪造与端点的连接,以便我调用该函数一次并且收到如下错误:
"KeyVaultErrorException: Operation returned an invalid status code 'NotFound'"
未提供端点。
如果我取消注释最后一行 (mock.Verify(x => x.GetKeyVaultValue(It.IsAny<string>()), Times.Once());)
我得到这个:
"Expected invocation on the mock once, but was 0 times:
x => x.GetKeyVaultValue(It.IsAny<string>())"
如果你认为你的 class 只不过是一个不起眼的包装器,这样其他 class 就可以在不知道 KeyVaultClient
的情况下依赖 ISomethingThatReturnsValues
然后嘲笑 KeyVaultClient
没有必要。我们只需要走这么远。我们不需要对我们依赖的框架 class 进行单元测试。
换句话说,我们是否需要验证
keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }")
...实际调用端点来获取密钥?确实如此。这就是 KeyVaultClient.GetSecretAsync(string)
所做的。如果测试失败,我们该怎么办?我们无法修复 class。同样,如果我们沿着这条路得出合乎逻辑的结论,我们将不得不测试各种东西。当我们创建一个 List<string>
并添加一个字符串时,它真的被添加了吗?我们不测试这些东西,因为它们已经过测试,因此可以合理地假设它们按预期工作。
对此的一个很好的测试是集成测试,它可以验证您的 class 是否符合预期。或者,如果另一个 class 依赖于此,您可以为该 class 编写一个集成测试,如果您无法从密钥库中检索到任何内容,该测试将会失败。但这不需要任何嘲笑。
对 而非 单元测试 class 感到满意的一部分是将其最小化,以便它除了调用内部 class 之外什么都不做。在这种情况下,您可以将 public
和 private
方法折叠成一个 public
方法,也许再提供一个 async
选项:
public async Task<string> GetSecretValueAsync(string keyName)
{
return await keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }");
}
public string GetSecretValue(string keyName) => GetSecretValueAsync(keyName).Result;
现在更明显了:这个 class 本身并没有真正做任何需要单元测试的事情。它的有用目的是使 KeyVaultClient
适应您的界面 - IKeyVaultConnection
以便其他 class 不直接依赖于 KeyVaultClient
.
我需要模拟 Key Vault 的端点,以便知道我是否正在调用函数来获取 Key Vault 一次。
我正在使用 C# 和 Moq(框架)开发它以进行测试。
界面如下:
public interface IKeyVaultConnection
{
string GetKeyVaultValue(string variableName);
}
public class KeyVaultConnection
{
public KeyVaultClient keyVaultClient;
private string endpointKeyVault;
public KeyVaultConnection(string keyVaultAddress = "DefaultEndpoint")
{
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
endpointKeyVault = $"https://{ keyVaultAddress }.vault.azure.net";
}
private async Task<string> AsyncGetSecretValue(string keyName)
{
var secret = await keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }")
.ConfigureAwait(false);
return secret.Value;
}
public string GetKeyVaultValue(string variableName)
{
Task<string> task = Task.Run(async () => await AsyncGetSecretValue(variableName));
task.Wait();
return task.Result;
}
}
Mock<IKeyVaultConnection> mock = new Mock<IKeyVaultConnection>();
//----->>>>>> Need to setup the endpoint
mock.Setup(x => x.GetKeyVaultValue(It.IsAny<string>())).Returns(It.IsAny<string>());
// mock.Verify(x => x.GetKeyVaultValue(It.IsAny<string>()), Times.Once());
我需要的是伪造与端点的连接,以便我调用该函数一次并且收到如下错误:
"KeyVaultErrorException: Operation returned an invalid status code 'NotFound'"
未提供端点。
如果我取消注释最后一行 (mock.Verify(x => x.GetKeyVaultValue(It.IsAny<string>()), Times.Once());)
我得到这个:
"Expected invocation on the mock once, but was 0 times:
x => x.GetKeyVaultValue(It.IsAny<string>())"
如果你认为你的 class 只不过是一个不起眼的包装器,这样其他 class 就可以在不知道 KeyVaultClient
的情况下依赖 ISomethingThatReturnsValues
然后嘲笑 KeyVaultClient
没有必要。我们只需要走这么远。我们不需要对我们依赖的框架 class 进行单元测试。
换句话说,我们是否需要验证
keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }")
...实际调用端点来获取密钥?确实如此。这就是 KeyVaultClient.GetSecretAsync(string)
所做的。如果测试失败,我们该怎么办?我们无法修复 class。同样,如果我们沿着这条路得出合乎逻辑的结论,我们将不得不测试各种东西。当我们创建一个 List<string>
并添加一个字符串时,它真的被添加了吗?我们不测试这些东西,因为它们已经过测试,因此可以合理地假设它们按预期工作。
对此的一个很好的测试是集成测试,它可以验证您的 class 是否符合预期。或者,如果另一个 class 依赖于此,您可以为该 class 编写一个集成测试,如果您无法从密钥库中检索到任何内容,该测试将会失败。但这不需要任何嘲笑。
对 而非 单元测试 class 感到满意的一部分是将其最小化,以便它除了调用内部 class 之外什么都不做。在这种情况下,您可以将 public
和 private
方法折叠成一个 public
方法,也许再提供一个 async
选项:
public async Task<string> GetSecretValueAsync(string keyName)
{
return await keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }");
}
public string GetSecretValue(string keyName) => GetSecretValueAsync(keyName).Result;
现在更明显了:这个 class 本身并没有真正做任何需要单元测试的事情。它的有用目的是使 KeyVaultClient
适应您的界面 - IKeyVaultConnection
以便其他 class 不直接依赖于 KeyVaultClient
.