0

I want to create a custom appsettings.json file for Unit testing in a MSTest project. I'm calling it testsettings.json. I'm using the repository pattern in the main project, so I have an IUnitOfWork and UnitOfWork that I declare as a Singleton in the ASP.NET side. This gives access to various repositories.

This UnitOfWork does rely on a few config variables, so I use appsettings.json to store these. So, the constructor for the UnitOfWork accepts an IConfigurationvariable that gets parsed via dependency injection and all those smart things in ASP.NET.

However, in the MSTest project there's no such. So, I need to create the IConfiguration object myself to use the constructor. I looked at the code from these StackOverflow links:

How can I create an instance of IConfiguration locally?

Populate IConfiguration for unit tests

How can I add a custom JSON file into IConfiguration?

Using IConfiguration in C# Class Library

However, the problem is that in NET Core 3.1 I can't use this:

IConfigurationRoot configuration = new ConfigurationBuilder()
            .SetBasePath([PATH_WHERE_appsettings.json_RESIDES])
            .AddJsonFile("appsettings.json")
            .Build();

So, I've resorted to reading in the JSON file as a string, converting it to a Dict<string, string> using Newtonsoft.Json and then parsing it to the config file. It's not optimized, and I have to use a different appsettings.json structure because I cannot parse JSON objects to the string dictionary.

So I have to do this:

{
  "var1": "abc",
  "var2": "def",
  "var3": "hij"
}

Instead of this:

{
  "obj1": {
    "var1": "abc",
    "var2": "def",
    "var3":  "hij"    
  }
}

Here's my implementation:

//setup logger
//-------------------
var loggerFactory = LoggerFactory.Create(builder =>
    {
    builder
       .AddFilter("Microsoft", LogLevel.Warning)
       .AddFilter("System", LogLevel.Warning)
       .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug);
    });
    testLogger = loggerFactory.CreateLogger<LocationRecordManagerTests>();
    testLogger.LogInformation("Init FileName of unit tests");

//setup config file
//-------------------
//get file path
string liveFolder = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
string projectFolder = Directory.GetParent(liveFolder).FullName;
string filePath = Path.Combine(projectFolder, "testsettings.json");
//get json as string data
string jsonString = File.ReadAllText(filePath);
Dictionary<string, string> jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonString);
//build config file
var configBuilder = new ConfigurationBuilder();
configBuilder.AddInMemoryCollection(jsonDict);
var configFile = configBuilder.Build();
//test - this works
//object value = configFile.GetSection("var1");

//create UnitOfWork
//-------------------
testUnitOfWork = new UnitOfWork(testLogger, configFile);

Edit - Solve the Json object parsing problem above

To solve the problem of adding Json objects into my testsettings.json I used the following:

//setup config file
//-------------------
//get file path
string liveFolder = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
string projectFolder = Directory.GetParent(liveFolder).FullName;
string filePath = Path.Combine(projectFolder, "testsettings.json");
//get json as string data
string jsonString = File.ReadAllText(filePath);
Dictionary<string, string> jsonDict, jsonObjectValues;

try
{

    Dictionary<string, object> objectDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonString);

    //get objectData
    object objectData = objectDict.GetValueOrDefault("RecordCollections");
    var jsonObjectData = JsonConvert.SerializeObject(recordData);
    jsonObjectValues = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonRecordData);

    //then use objectDict.Remove(..) to remove duplications

    jsonDict = objectDict.ToDictionary(x => x.Key, x => x.Value.ToString());
}
catch (Exception e)
{
    throw e;
}

//build config file
var configBuilder = new ConfigurationBuilder();
//add variables from json file
configBuilder.AddInMemoryCollection(jsonDict);
// add object variables from json file
configBuilder.AddInMemoryCollection(jsonObjectValues);
var configFile = configBuilder.Build();

Does anyone know how to do this in a better way?

itstudes
  • 411
  • 5
  • 14
  • 1
    Why can't you reference it the way that you say you can't? It's definitely possible so I think you need to explain why it isn't for you – pinkfloydx33 May 11 '20 at 18:22
  • Why just don't use [`MemoryConfigurationSource`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.memory.memoryconfigurationsource?view=dotnet-plat-ext-3.1) for that? – Pavel Anikhouski May 11 '20 at 18:29
  • @pinkfloydx33, so when I have items in an object in JSON (as shown with obj1) then I get an error with JsonConvert. It says "Unexpected character encountered while parsing value: {. Path 'obj1', line 2, ...." I can't use a string,object Dictionary because the configBuilder only accepts a string,string type. – itstudes May 11 '20 at 19:41
  • @PavelAnikhouski I'm not really understanding how that would be beneficial. Would I not still have to parse my testsettings.json into an IConfigurationBuilder and then use that with MemoryConfigurationSource? – itstudes May 11 '20 at 19:47
  • @itstudes You can fill the `InitialData` dictionary in `MemoryConfigurationSource`, without parsing json file – Pavel Anikhouski May 11 '20 at 19:50
  • @pinkfloydx33 as you pointed out, I could get the data out of the object. It just required a bit of hacky code. See my edits. – itstudes May 13 '20 at 09:21

1 Answers1

0

See this Configuration in ASP.NET Core example about bind-hierarchical-configuration-data-using-the-options-pattern. No matter put configs in either appsettings.json or testsettings.json, you can bind them into your model, named UnitOfWorkModel for an example below.

appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  },
  "UnitOfWork": {
    "var1": "abc",
    "var2": "def",
    "var3": "hij"
  }
}

testsettings.json

{
  "var1": "abc",
  "var2": "def",
  "var3": "hij"
}

You can first declare a binding model for configuration

public class UnitOfWorkModel
{
    public string Var1 { get; set; }
    public string Var2 { get; set; }
    public string Var3 { get; set; }
}

Then, bind into UnitOfWorkModel from an native IConfiguration object while .NET Core Startup

public Startup(IConfiguration configuration)
{
    IConfigurationSection section = configuration.GetSection("UnitOfWork");
    IConfiguration configuration = section as IConfiguration;

    IEnumerable<IConfigurationSection> items = configuration.GetSection("UnitOfWork").GetChildren();
    // items.Count() == 3

    Dictionary<string, string> dict = items.ToDictionary(o => o.Key, o => o.Value);
    // dict.Count() == 3

    var unitOfWorkModel = new UnitOfWorkModel();
    section.Bind(unitOfWorkModel);
    // unitOfWorkModel.Var1 == "abc";
    // unitOfWorkModel.Var2 == "def";
    // unitOfWorkModel.Var3 == "hij";
}

Or, test the binding result via a unittest (here is an example of xUnit testing framework for the .NET Core with dependency, Microsoft.Extensions.Configuration.Json NuGet package)

[xUnit]
public void BindTest()
{
    IConfigurationRoot root = new ConfigurationBuilder()
        .AddJsonFile("testsettings.json")
        .Build();

    IConfiguration configuration = root as IConfiguration;
    Assert.NotNull(configuration);

    var unitOfWorkModel = new UnitOfWorkModel();
    root.Bind(unitOfWorkModel);

    Assert.Equals("abc", unitOfWorkModel.Var1);
    Assert.Equals("def", unitOfWorkModel.Var2);
    Assert.Equals("hij", unitOfWorkModel.Var3);
}

Either way should work fine with the super interface IConfiguration.

nwpie
  • 665
  • 11
  • 24
  • From what I've seen the .AddJsonFile(..) functionality is not available in Net Core 3.1 so you cannot build the file as you have stated for the xUnit testing framework. – itstudes May 13 '20 at 09:10
  • @itstudes Maybe you can nuget the `Microsoft.Extensions.Configuration.Json` package, then probably can see `.AddJsonFile` extension method in the namespace of `Microsoft.Extensions.Configuration` – nwpie May 13 '20 at 10:07