Many are now familiar with using typed configuration that's available in .net core and .net 5 with the help from Options pattern. However, if you are working on a projects targeting .NET Framework, you will know ConfigurationSection from System.Configuration assembly.

ConfigurationSection allows you to group related configurations together in xml. Lets take an example of providing some simple configuring settings to a retry client.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="retrySettings" type="ConfigSectionTest.RetrySettings, ConfigSectionTest" />
  </configSections>
  <retrySettings count="3" delay="00:00:30" failureThreshold="0.5" />
</configuration>

Notice the configSections.section node where we are naming the config section and also pointing to the type where its implemented. retrySettings xml node has various attributes and values assigned for each.

The RetrySettings class is implemented as...

public class RetrySettings : ConfigurationSection
{
  [ConfigurationProperty("count")]
  public int RetryCount => (int)this["count"];

  [ConfigurationProperty("delay", IsRequired = true)]
  public TimeSpan RetryDelay => (TimeSpan)this["delay"];

  [ConfigurationProperty("failureThreshold")]
  public decimal FailureThreshold => (decimal)this["failureThreshold"];
}

With expression bodied property syntax, the class is fairly small. I've specified RetryDelay to be required using the ConfigurationProperty attribute.

In order to use the configuration section RetrySettings, you'd do so by calling ConfigurationManager.

ConfigurationManager.GetSection("retrySettings") as RetrySettings;

Even though you can create an instance of RetrySettings, it is not possible set the values of the properties without modifying the class (providing a setter). You will be able to mock the class by introducing an interface or using an actual appsettings.config file.

However, it maybe easier to create a fake retry settings config section instead of mocking. In order to do this, you can inherit the ConfigurationSection class which gives you access to the DeserializeElement method. This method takes in an xml string which is your section node. The second argument indicates whether to serialize only the collection key property.

class FakeRetrySettings : RetrySettings
{
  public void PopulateConfig(string xmlString)
  {
    using (XmlReader reader = new XmlTextReader(new StringReader(xmlString)))
    {
      reader.MoveToContent();
      DeserializeElement(reader, serializeCollectionKey: false);
    }
  }
}

You can now configure the retry settings as needed to orchestrate your system under test.

var retrySettings = new FakeRetrySettings();
const string xmlString = "<retrySettings count=\"3\" delay=\"00:05:00\" />";
retrySettings.PopulateConfig(xmlString);

You can make it much more easy to work with the RetrySettings class in test project by having a builder class.

class RetrySettingsBuilder
{
  private readonly Dictionary<string, string> properties = new Dictionary<string, string>
  {
    { "count", "3" },
    { "delay", "00:30:00" },
    { "failureThreshold", "0.5" }
  };
        
  public RetrySettingsBuilder WithProperty(string key, string url)
  {
    if (!properties.ContainsKey(key))
      throw new ArgumentException(nameof(key));

    properties[key] = url;

    return this;
  }
        
  public RetrySettings Build()
  {
    var xmlString = "<retrySettings ";
    var pairs = properties.Select(pair => $"{pair.Key}=\"{pair.Value}\"");
    xmlString += string.Join(" ", pairs) + " />";

    var settings = new FakeRetrySettings();
    settings.PopulateConfig(xmlString);
    return settings;
  }
}

Builder has some default values already configured. Therefore, if you wanted the test to work and you don't worry about the values, you can create a new instance of the configuration section by calling new RetrySettingsBuilder().Build();. You can override the default value by calling WithProperty method e.g. new RetrySettingsBuilder().WithProperty("count", "2").Build();.