Search code examples
c#.netvisual-studiounit-testinginterface

Abstracting/Interfacing a class within a Nuget Package for Unit Testing


So I have a Application that currrently uses the EAGetMail nuget package for scraping EmailServers. I am attempting to refactor my project and interface individual pieces for mocking in Unit Tests. My current issue is the classes in the package don't seem to be interfaced and I am not sure if interfacing them indirectly would work. Could just be a lack of understanding or a different way tenter image description hereo mock the data is needed. Anything would help!

enter image description here

enter image description here


Solution

  • As the classes are also sealed, there are no options to create mocks directly. If you are refactoring your codebase anyway, your best option would be to put all the email related stuff in wrapper classes and create an interface for these wrappers.

    Mocking those interfaces is easy, plus you have the code that depends on the component assembled in one place. If you decide to move on to another package later on, your changes are better that you only need to change the wrapper classes and not several spots in your codebase.

    As regards unit testing the wrapper classes, those should be as thin as possible. The package publisher should unit test its components, so there would not be anything left to test in the wrappers.


    As regards the example method Login, the method comprises three blocks:

    1. The code that determines the host server. This is your code that you need to test. However, testing this code is easy, because there are no dependencies to the package.
    2. The setup code for the MailServer class. This code uses code from the package and creates the MailServer. You could test whether the settings are correct. You would not have to mock anything here, because AFAIK MailServer should be more or less like a DTO. So you could test whether your code sets the correct options.
    3. The code that connects to the mail server. This code uses the methods of the package. If the publisher tests its code thorougly then testing it again would not be too valuable.

    To provide some code, a rough outline could be the following:

    public class HostNameProvider
    {
      public virtual string GetHostNameFromMailAddress(string mailAddress)
      {
        // Here goes the Substring and Switch
        // You can create unit tests that check 
        // whether the output of the method meets your expectations
      }
    }
    
    public class MailServerBuilder
    {
       public virtual MailServer Build(string userName, ...)
       {
         // This helper class is used by your wrapper for the library
         // You can create a test for the method and check the 
         // MailServer object
       }
    }
    
    public interface IMailClientWrapper 
    {
      void Connect(string userName, ...);
    }
    
    public class MailClientWrapper : IMailClientWrapper
    {
      private readonly MailServerBuilder _bldr;
      
      public MailClientWrapper(MailServerBuilder bldr)
      {
        _bldr = bldr;
      }
    
      public void Connect(string userName, ...)
      {
         var server = _bldr.Build(userName, ...);
         // Connect to the server
         // There would be no unit tests for this code 
         // as it relies on sealed classes
      }
    }
    
    public class LoginService  // or how your class is named 
    {
      private readonly HostNameProvider _hostNameProv;
      private readonly IMailClientWrapper _mailClient;
      
      public LoginService(
        HostNameProvider hostNameProv, 
        IMailClientWrapper mailClient)
      {
        _hostNameProv = hostNameProv;
        _mailClient = mailClient;
      }
    
      public bool Login()
      {
        var hostName = _hostNameProv.GetHostNameFromMailAddress(mailAddress);
        try
        {
          _mailClient.Connect(userName, ...);
        }
        catch 
        {
          return false;
        }
        return true;
      }
    }
    

    Above sample splits the code into several blocks; most of them can be tested.