View on GitHub

Patrickhuber.github.io

blogs

Motivation

I like the idea of removing user secrets from my applications during development as pointed out in the following microsoft documentation. This techneque works great for web application development, but what about tests? I often will create integration tests that contain sensetive information to test out connecting to an external service, so why not do the same?

Setup

Create the project

First, Create a .NET core unit test project.

Install NuGet Packages

Next, add the following nuget package

Install-Package "Microsoft.Exensions.Configuration.UserSecrets"

Add the UserSecretId

Similar to the setup in this article, right click on the project and add the following line.

<PropertyGroup>
    <UserSecretsId>{test project identifier}-{guid}</UserSecretsId>
</PropertyGroup>

Replace the {test project identifier} with the name of your test project. This name is just to keep the secret store unique, it can really be anything.

Replace the {guid} with a unique identifier. You can generate one here: https://www.guidgenerator.com/online-guid-generator.aspx

Here is an example:

<PropertyGroup>
    <UserSecretsId>myproj-integration-tests-bf4e2e40-30e1-42e6-87ca-9f88390a414c</UserSecretsId>
</PropertyGroup>

Add the tooling reference

Also in the same csproj file add the following reference

<ItemGroup>
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.1" />
</ItemGroup>

Create your first secret

Open the command prompt to the directory of the .csproj file. Add a secret by running:

dotnet user-secrets set ApiUserName someguy
dotnet user-secrets set ApiPassword abc123

Setup the test to read the secret

Now that we have a secret in the secret store, we can read it using the configuration libraries. I’m using MSTest but the same should apply for any testing framwork.

using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace MyAwesomeLibrary.Tests.Integration
{
    [TestClass]
    public class HttpClientTests
    {
        IConfiguration Configuration { get; set; }

        public HttpClientTests()
        {
            // the type specified here is just so the secrets library can 
            // find the UserSecretId we added in the csproj file
            var builder = new ConfigurationBuilder()
                .AddUserSecrets<HttpClientTests>();

            Configuration = builder.Build();
        }

        [TestMethod]
        public async Task HttpClientShouldAllowBasicAuthentication()
        {            
            var username = Configuration["ApiUsername"];
            var password = Configuration["ApiPassword"];

            // this test url just validates the parameters you pass in the uri,
            // normally you wouldn't do this
            var requestUri = $"http://httpbin.org/basic-auth/{username}/{password}";

            using (HttpClient client = new HttpClient())
            { 
                var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
            
                var credential = $"{username}:{password}";
                var credentialBytes = Encoding.ASCII.GetBytes(credential);
                var credentialBase64 = Convert.ToBase64String(credentialBytes);

                request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentialBase64);

                var response = await client.SendAsync(request);

                Assert.AreEqual(200, (int)response.StatusCode);
            }
        }
    }
}