Blogger news

Blogger templates

Sunday, September 3, 2017

Even Config file is a dependency to unit test, which you should Decouple and Mock


I had a discussion with one of my friends today on the config files and unit testing. The doubt he had was Config is the part of the solution then how it will be a dependency? we are reading config values through a static property, (ConfigurationManager.AppSettings["Key"] in C# ) then how we can decouple/mock the code from reading values from config? His question was really valid and I have seen many codes where they write perfect unit test but failed to decouple Config. To answer his question, Yes definitely config file is a dependency and we should decouple it and mock it while writing unit tests.

The basic and most important rule of unit testing is that a unit test should not have any kind of dependencies. It can be internal dependencies (Like .Config files) or external dependencies (Like a database, external API’s etc. ). To be more specific if we disconnect all the wires / Wi-Fi and if we delete all the files other than class files still all your unit tests should pass. All right let us do some coding now. Firstly let us see the problem and will refactor to fix it.
Let me write the simplest example in the world. The example code will read a server path and file name from config and concatenate it. I will have a unit test to test the logic of concatenation. So let's start coding. Say I have config entries like,

   <appSettings>
    <add key="ServerPath" value="C:\Images\"/>
    <add key="Filename" value="CodeWithVijay.jpg"/>
  </appSettings> 
 
And I have business logic to read data from config and concatenate it, which looks like below,

    public class CoupledServerFileDetails
    {
        public string GetFile()
        {
            return ConfigurationManager.AppSettings["ServerPath"] +
                   ConfigurationManager.AppSettings["Filename"];
        }


Above piece of code is the classic example of tightly coupling with the config file. I will explain what all are the problems with above code after I write unit test for the same
My unit test would look like,

 [TestMethod]
 //This is not a unit test as we are not mocking the depednecy
 public void CoupledFileNameTest()
  {
      //Arrange
      ServerFileDetails serverFileDetails = new ServerFileDetails();
      string expectedFile = @"C:\Images\CodeWithVijay.jpg"; 

      //Act
      string actualFile = serverFileDetails.GetFile();

      //Assert
      //If I give any other value other than C:\Images\CodeWithVijay.jpg
     //My test will fail as my test is testing actual data as well
     //which is not at all good for unit test
      Assert.AreEqual(expectedFile, actualFile);
  }
 
When I run the unit test it will definitely go and hit my config file as the code is tightly coupled with the config file, it mainly means,
  • Firstly and most importantly the test I have written is not unit test but it is an integration test.
  • My code is tightly coupled and there is no way I can replace config file with something else (Say a database) without changing actual logic. It means I am violating ‘Open-Closed’ principle'.
  • My actual business logic has an additional responsibility of reading data from a file (.config) along with real business logic. It means I am violating 'Single Responsibility Principle'.
  • My unit test failure or success are based on the values of the config file. It means I am testing config file as well, which is not required. A unit test is not supposed to test actual data but is to test business logic based on the data I am setting up.
  • If config values vary based on the environment than my so called unit test will fail in those environments (Explanation of point c). should be environment agnostic.
So how do we solve it? As I told before we don’t have any kind of abstraction to decouple ConfigurationManager.AppSettings as it is static property.

A note of caution to consider while you design your class - If you are 101% sure that if your state or behaviour will not behave polymorphically then only go for static. For example, the value of a universal constant (say the boiling point of water, which is 100 °C ) which will never need to behave polymorphically then go for static. If you 0.1% doubt about polymorphic behaviour then go for static.

So let's start thinking about solving the puzzle. As always and always Interface is your friend. Polymorphism is the real beauty of object oriented programming. The polymorphism we want to achieve is to inject a mock object and that mock object will have a mock behaviour to get mock data instead of getting values from the config file. So we have a plan and solution in hand now. Let's code for it. Let me start with my best friend in object oriented programming, the interface

public interface IFileConfig
 {
        string FilePath { get; }
        string FileName { get; }
 }
 
I have created an interface with two read only properties. The reason for read-only properties because of course, I don't need to set any values for config data. Let me create a class next, which implements IFileConfig interface,

public class FileConfig : IFileConfig
{
 public string FilePath => ConfigurationManager.AppSettings["ServerPath"];

 public string FileName => ConfigurationManager.AppSettings["Filename"];
}
 
The above code means I have moved the responsibility of reading data from config to another class which implements IFileConfig. Now we have an interface. It means I can create a mock object of that interface in unit test right? Well yes but before that, we have one more major bit to do, which is to inject the dependency to our business logic. Let's do that next.

 public class DeCoupledServerFileDetails
 {
        private readonly IFileConfig _fileConfig;

        public DeCoupledServerFileDetails(IFileConfig fileConfig)
        {
            _fileConfig = fileConfig;
        }

        public string GetFile()
        {
            return _fileConfig.FilePath + _fileConfig.FileName;
        }
  }
 
 So we have injected IFileConfig now. It means 'DecoupledServerDetails' class don't know where it is reading data from. In fact, it doesn't need to know. It can be any class which implements IFileConfig interface. Remember Dependency Inversion Principle, "High-level modules should not depend on low-level modules. Both should depend on abstractions". Now we got a perfect design which we can mock the dependency for unit test. So let's write unit test. I will create a manual mock first and then will mock it using Moq, which is a dependency mocking framework. Let me create a mock class first, which implements IFileConfig interface,

public class MockFileConfig : IFileConfig
{
    public string FilePath => @"TestPath\";

    public string FileName => "TestFileName";
}
 
As you can see I have configured my test data in the mock class, which is "TestPath\" and "TestFileName". Now let me write the unit test to pass the mock dependency,

[TestMethod]
public void DeCoupledServerFileNameTestWithManualMocking()
{
   //Arrange
   IFileConfig mockConfig = new MockFileConfig();

   DeCoupledServerFileDetails serverFileDetails = 
                              new DeCoupledServerFileDetails(mockConfig);

   string expectedFile = @"TestPath\TestFileName";

   //Act
  string actualFile = serverFileDetails.GetFile();

  //Assert
          
  Assert.AreEqual(expectedFile, actualFile);
}
 
So the expected value is the concatenation of our test data not the values from config and you can see the test passing. Excellent we have successfully decoupled our business logic from the config dependency hence we have written a perfect unit test.
Next, I will show how I can mock with a mocking framework (Moq) instead of manual mocking. You need to add a NuGet package "Moq" first and then,

 [TestMethod]
 public void DeCoupledServerFileNameTestWithMOQ()
 {
    //Arrange
    Mock<IFileConfig> mockConfig = new Mock<IFileConfig>();
    mockConfig.SetupGet(a => a.FilePath).Returns(@"TestPath\");
    mockConfig.SetupGet(a => a.FileName).Returns("TestFileName");

    DeCoupledServerFileDetails serverFileDetails = new DeCoupledServerFileDetails(mockConfig.Object);
    string expectedFile = @"TestPath\TestFileName";

    //Act
    string actualFile = serverFileDetails.GetFile();

    //Assert
    Assert.AreEqual(expectedFile, actualFile);
  }
 
With this design, if I want to read data from other resources, say a database instead of the config file still there will not be any change to the business logic class I have written. The only thing we should do is to create another class which implements IFileConfig interface and writes logic to get data from database in that class.

You can download the code from my GitHub repository- https://github.com/vnathv/decouplecodefromconfigfile.git

A final note- Your unit shouldn't have any kind of dependencies. If your unit test has a dependency then your test is not unit test but it is an integration test.

1 comment:

Post a Comment