1

I hope this is decent question, the background is that I have an abstract base class, this base class has some concrete method implementations which are accessible to derived classes. The base class and the all of the derived classes have a set of unique variables. All of the common functionality is in the concrete methods of the base class (Which are NOT virtual so they cannot be overridden) Each of the derived classes are also getting some of these values from the AppSettings in the App.Config file.

So my question is this, Where is the best place to put these const values that would be following best practices and will allow the code to be properly testable (this last piece is very important)?

The 2 options that I know are:

1) Create a static config class that has all of the values for each of the classes. This is great for the normal consts, but retrieving the App.Config values will fail unless, either the Test project has the values in its own App.Config file (This seems like a dirty hack to me)

2) The other option is to declare them and get the values settings in the using class, this adheres to the declare it where you use it principle, but it makes the class un-testable unless you again add the values to the App.Config of the test project.

public abstract class BaseClassA
{

    private const string PATH = "\ParentClassPath\" // Option 2
    private int _var1;
    private string _var2;
    public BaseClassA(int param1, string param2)
    {
        _var1 = param1;
        _var2 = param2;
    }
    public int Param1Prop { get; private set;}
    public string Param2Prop { get; private set; }

    protected string Method1(string value1, string value2, string value3)
    {
        Directory.CreateDirectory(StaticClass.PATH); //Option 1 
        return Path.GetDirectoryName(PATH) //Option 2
    }
}

public class DerivedClassB
             : base(1, "param2")
{

    private const string PATH = "\DerivedClassPath\" // Option 2

    public BaseClassA(int param1, string param2)
    {
        _var1 = param1;
        _var2 = param2;
    }
    public int Param1Prop { get; private set;}
    public string Param2Prop { get; private set; }

    protected string Method1(string value1, string value2, string value3)
    {
        Directory.CreateDirectory(StaticClass.DERIVED_PATH); //Option 1 
        return Path.GetDirectoryName(PATH) //Option 2
    }
}
Kelso Sharp
  • 972
  • 8
  • 12
  • 1
    Can you add some code? I know you the existing code you have is probably long, but if you could show some example code it would really help to visualize what you have going on. – MichaelDotKnox Sep 22 '16 at 18:07
  • If I have understood you correctly, You could put them in your App.Config and have a service to retrieve them. Include an interface for your configuration service, and mock the return result to suit your testing needs. –  Sep 22 '16 at 18:29
  • Configuration is not a service its a static class, its not possible to get the values from the app.config file unless the settings are also copied to the App.Config in the test project which doesn't really test the code, because if the App.Config file changes in the application the tests will still pass if no one updates the App.Config in the test project – Kelso Sharp Sep 22 '16 at 18:41
  • @Kelso Sharp - you misunderstand, if you wrap the static configuration manager in your own custom service, you can then mock the return results of that service so that your tests do not need access to your config values directly. Please tell me if this does not make sense and I'll put up a more detailed answer. –  Sep 22 '16 at 19:30
  • @Dan-Cook If you wouldn't I would appreciate that. – Kelso Sharp Sep 22 '16 at 19:37
  • Updated the question to make it more applicable – Kelso Sharp Sep 22 '16 at 20:08

2 Answers2

2

Create a custom class that wraps your call to ConfigurationManager.AppSettings["key"] with an interface. Mock that interface in your test so that you can define which values you need to test. Rough example to explain (untested):

public interface IConfigurationService
{
    string Get(string key);
}


public class ConfigurationService :IConfigurationService
{
    public string Get(string key)
    {
        return ConfigurationManager.AppSettings[key];
    }
}

In your tests you can now mock your interface as such:

public void NaiveTest()
{
    var key = "someKey";
    var result = "someValue";
    var mockConfigurationService = new Mock<IConfigurationService>();
    mockConfigurationService.Setup(x => x.Get(key)).Returns(result);

   // pass service to class being tested and continue
}

Include your configuration service in your base class, then each derived class can pull their values as needed. You can now test any value you like through the use of Mocks. More here: https://github.com/moq/moq4

Please let me know if you need further advice.

  • I did try this, I was not able to get it to resolve for appsettings, I thought maybe you were talking about something different. I will try again, just to be certain. – Kelso Sharp Sep 22 '16 at 19:49
  • @KelsoSharp it sounds like you may be missing a project reference - http://stackoverflow.com/questions/19232695/how-to-use-configurationmanager –  Sep 22 '16 at 19:52
  • Thank you! The key to the answer is including it in my base class, I had not done that before. That made it work, significantly reduces the amount of code I need to write to test. – Kelso Sharp Sep 22 '16 at 20:02
  • @KelsoSharp you're welcome, glad to help :) –  Sep 22 '16 at 20:03
0

From a testing perspective this sounds like a job for Fakes. You can add a shim to intercept the calls to read the config values, then you can code multiple possible values within your tests without requiring a config file at all.

Here is an article about how they work. https://msdn.microsoft.com/en-us/library/hh549175.aspx

Here's a partial example of how it would look in your unit test if you were trying to intercept reads from the registry: (I didn't have any examples that use config files handy, but you get the idea.)

using (ShimsContext.Create())
{
    Microsoft.Win32.Fakes.ShimRegistryKey.AllInstances.GetValueString = (key, valueName) =>
    { return "SomeValue"; };
    Microsoft.Win32.Fakes.ShimRegistryKey.AllInstances.OpenSubKeyStringBoolean = (key, subkey, write) =>
    {
        var openKey = new Microsoft.Win32.Fakes.ShimRegistryKey();
        openKey.NameGet = () => Path.Combine(key.Name, subkey);
        return openKey;
    };
    // Exercise the code under test that reads from the registry here. 
    // Make Assertions
}

So in this example, when the code tries to make a registry call to open the registry key, it instead runs the OpenSubKeyStringBoolean lambda. Then when it calls GetValue on the returned key, it runs the GetValueString lambda instead. So the result is "SomeValue" is returned instead of whatever is (or isn't) at that registry key.

Anything within the using for the ShimsContext runs your shim code. As soon as the ShimsContext is disposed, things go back to normal.

Steve In CO
  • 5,746
  • 2
  • 21
  • 32
  • Ok, if using fakes, what is the best place to put the const values? In the deriving class or in the static config class that has a method for getting AppSettings from the App.Config file? – Kelso Sharp Sep 22 '16 at 18:45
  • I guess I am not clear on your use of const vs a config file. Are they values that never change? Or is the const just a default value? If the const is just a default value, then perhaps pass in the const of the derived class to the method which retrieves the value from the config file so it will be returned in the case where the config doesn't have an entry? – Steve In CO Sep 22 '16 at 18:55
  • I might not be clear, the consts within the derived class are specific, but some of those are being set based on a call to the ConfigurationManager.AppSettings["key"] These are always null in testing in both cases because they have to be in the App.Config of the Test project, and like you said in your answer a good option for a fake, the question is if I should use the Static Config *class* (not file) or in the deriving class. – Kelso Sharp Sep 22 '16 at 19:00
  • I want to give credit here, because this will work, and its more a matter of opinion, and it would be unfair not to acknowledge this answer as well. – Kelso Sharp Sep 22 '16 at 20:10