Search code examples
c#azureunit-testingmoqazure-keyvault

How to unit test with Moq the Azure Key Vault


I need to mock the endpoint of the Key Vault in order to know if I am calling the function to get the key vault once.

I am developing this with C# and Moq (Framework) in order to do the testing.

The interface is the following:

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());

What I need is to fake the connection to the endpoint in order to get that I am calling that function once and I am getting errors like:

"KeyVaultErrorException: Operation returned an invalid status code 'NotFound'"

for not providing the endpoint.

If I uncomment last line (mock.Verify(x => x.GetKeyVaultValue(It.IsAny<string>()), Times.Once());) I get this:

"Expected invocation on the mock once, but was 0 times: 
 x => x.GetKeyVaultValue(It.IsAny<string>())"

Solution

  • If you view your class as nothing but a humble wrapper so that other classes can depend on ISomethingThatReturnsValues without knowing about KeyVaultClient then mocking KeyVaultClient isn't necessary. We only need to go so far. We don't need to unit test framework classes that we depend on.

    In other words, do we need to verify that

    keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }")
    

    ...actually calls the endpoint to get the secret? It does. That's what KeyVaultClient.GetSecretAsync(string) does. If the test fails, what can we do? We can't fix that class. Similarly, if we followed that road to its logical conclusion, we'd have to test all sorts of stuff. When we create a List<string> and add a string, does it really get added? We don't test those things because they're already tested so it's reasonable to assume that they work as expected.

    A good test for this would be an integration test which verifies that your class does what it expected. Or, if another class depends on this, you could write an integration test for that class which would fail if you can't retrieve anything from the key vault. But that wouldn't require any mocking.

    Part of being comfortable with not unit testing the class is minimizing it so that it really does nothing except invoke the inner class. In this case you could collapse your public and private method into one public method, and maybe one more to provide an async option:

        public async Task<string> GetSecretValueAsync(string keyName)
        {
            return await keyVaultClient.GetSecretAsync($"{endpointKeyVault}/secrets/{ keyName }");
        }
    
        public string GetSecretValue(string keyName) => GetSecretValueAsync(keyName).Result;
    

    Now it's more evident: This class doesn't really do anything of its own that needs unit testing. Its useful purpose is to adapt KeyVaultClient to your interface - IKeyVaultConnection so that other classes don't directly depend on KeyVaultClient.