8

I've got a static class that I am using for logging:

public static class myLogger
{
    public static ErrorLogging(string input)
    {
        //dostuff
    }
}

The way I am using it is:

public class myClassthatDoesStuff
{
    ...
    myLogger.ErrorLogging("some error ocurred");
    ...
}

How can I moq the myLogger class in order to be able to unit test it and ensure that the ErrorLogging method was executed? Is it possible to do this without setting any parameters in the constructor (constructor injection)? myClassthatDoesStuff requires that there are no parameters in the constructor.

Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062

4 Answers4

13

This blog post describes the exact same scenario - you have an old static logging method and want to use it in testable code.

  • Wrap the static class in a non-static class - not just for testing, but for general use.

  • Extract the methods of your new non-static class into an interface.

  • Wherever you would have depended on the static class, depend on the interface instead. For example, if class DoesSomething requires the function in your static class, do this:

      public interface ILogger
      {
          void ErrorLogging(string input);
      }
    
      public class MyClassthatDoesStuff
      {
          private readonly ILogger _logger;
    
          public MyClassthatDoesStuff(ILogger logger)
          {
              _logger = logger;
          }
      }
    

This gives you two benefits:

  1. You can unit test your old static class (assuming that it has no state and doesn't depend on anything that has any state) (although if that's the case I suppose you could unit test it anyway.)

  2. You can unit test code that will use that static class (by removing the direct dependency on that static class.) You can replace ILogger with a mocked class, like one that adds your error messages to a list.

    class StringLogger : List<string>, ILogger
    {
        public void ErrorLogging(string input)
        {
           Add(input);
        }
    }
    
    var testSubject = new MyClassthatDoesStuff(new StringLogger());
    

A simpler option doesn't require creating an interface and an adapter class. You can create a delegate which is like an interface for just a method.

In the case of the logger, it would be

delegate void Logging ErrorLoggingMethod(string input);

Using it looks similar to using an interface:

    public class MyClassthatDoesStuff
    {
        private readonly ErrorLoggingMethod _logger;

        public MyClassthatDoesStuff(ILogger logger)
        {
            _logger = logger;
        }

        public void DoSomethingThatLogs()
        {
            // _logger is a method
            _logger("Log something");
        }
    }

This is even easier to mock and test

string loggedMessage = null;
ErrorLoggingMethod fakeLogger = (input) => loggedMessage = input;

You can inject the fake logger into the class you're testing. If that class calls the logger, the method assigns whatever was logged to the variable. Then you can assert whatever was logged or just that anything was logged.

If your app uses a dependency injection/IoC container, you can register the delegate just like you would an interface. Here's an example.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • Is it possible to do this without setting any parameters in the constructor (constructor injection)? myClassthatDoesStuff requires that there are no parameters in the constructor. – Alex Gordon Mar 07 '16 at 16:21
  • If you can't put anything in the constructor that definitely makes it harder. If you can use dependency injection from the ground up that gives you some control over how your classes are instantiated. For example, without dependency injection a WCF service class usually couldn't have anything in its constructor. – Scott Hannen Mar 07 '16 at 16:28
  • scott thanks very much for explaining all of that. makes sense – Alex Gordon Mar 07 '16 at 16:34
4

If you can not change it from a static to a non-static class, wrap it with a non-static class...

void Test()
{
    string testString = "Added log";
    var logStore = new List<string>();
    ILogger logger = new MyTestableLogger(logStore);

    logger.ErrorLogging(testString);

    Assert.That(logStore.Any(log => log==testString));
}

public interface ILogger
{
    void ErrorLogging(string input);
}

public class MyTestableLogger : ILogger
{
    public MyTestableLogger(ICollection<string> logStore)
    {
        this.logStore = logStore;
    }

    private ICollection<string> logStore;

    public void ErrorLogging(string input)
    {
        logStore.Add(input);
        MyLogger.ErrorLogging(input);
    }
}

public static class MyLogger
{
    public static void ErrorLogging(string input)
    {
        // Persist input string somewhere
    }
}
Aydin
  • 15,016
  • 4
  • 32
  • 42
  • Is it possible to do this without setting any parameters in the constructor (constructor injection)? myClassthatDoesStuff requires that there are no parameters in the constructor. – Alex Gordon Mar 07 '16 at 16:21
  • 2
    @l--''''''---------'''''''''''' *(never knew `@`mentions could turn to such torture...)* You could remove the parameter sure... but then how would you test to see if the log entry you've added exists in the data store? :) – Aydin Mar 07 '16 at 17:01
2

You can do it using Microsoft's Shims

Assuming that Your Project is called ConsoleApplication1.
First of all go to your unit test project references, right click on assembly that contains myClassthatDoesStuff class and chose 'Add Fakes Assembly'.

Add Fakes Assembly

Unit test with shims will look like:

[TestClass()]
public class MyClassthatDoesStuffTests
{
    [TestMethod()]
    public void ImportansStuffTest()
    {
        using (ShimsContext.Create())
        {
            bool logCalled = false;
            ConsoleApplication1.Fakes.ShimmyLogger.ErrorLoggingString = 
                (message) => logCalled = true;
            new myClassthatDoesStuff().ImportansStuff();
            Assert.IsTrue(logCalled);
        }
    }
}
igorushi
  • 1,855
  • 21
  • 19
  • Once it was called Moles, its still available for VS2010 -https://visualstudiogallery.msdn.microsoft.com/b3b41648-1c21-471f-a2b0-f76d8fb932ee/ – igorushi Mar 06 '16 at 14:44
1

You can do it with Typemock Isolator.

It allows you to avoid all this amount of wrappers and interfaces and to do it that simple:

[TestMethod]
public void TestLogging()
{
    //Arrange
    Isolate.WhenCalled(() => myLogger.ErrorLogging("")).CallOriginal();

    //Act
    var foo = new myClassthatDoesStuff();
    foo.DoStuff();

    //Assert
    Isolate.Verify.WasCalledWithAnyArguments(() => myLogger.ErrorLogging(""));
}
JamesR
  • 745
  • 4
  • 15