tl;dr : Wrap theses methods inside the layer of abstraction. Then create a stub and use it to create test scenarios.

This post is based on my answer on the Stack Overflow - "How to unit test this function?"

The original question is about writing unit test for a code that uses Console methods inside its body. This problem is more general and in this post, I want to show one of the ways to unit test code with static method.

Example of code with static methods

This example is from the Stack Overflow question.

public static string GetMaskedInput()
{
    string pwd = "";
    ConsoleKeyInfo key;
    do
    {
        key = Console.ReadKey(true);
        if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
        {
            pwd += key.KeyChar;
            Console.Write("*");
        }
        else
        {
            if (key.Key == ConsoleKey.Backspace && pwd.Length > 0)
            {
                pwd = pwd.Substring(0, pwd.Length - 1);
                Console.Write("\b \b");
            }
        }
    }
    while (key.Key != ConsoleKey.Enter);
    return pwd;
}

Little Explanation

Method GetMaskedInput() does two things.

  • Whenever your press a key it shows you a '*' char
  • All the keys are also appended to the string and returned

It's a simple implementation of the hidden input. Something like a password text box on login screens.

Whats wrong with static method ?

This sample method is highly dependant on the Console.ReadKey and Console.Write methods. Code like this is only usable when we have access to console. Running it in for instance web.app enviroment would be problematic. This is one of the reasons that static methods are evil. Not like famous "goto", beacuse there are scenarios in which statics are ok.

  • extension methods
  • simple helpers with input, output contained in the scope of the function (no globals, external dependancies )

Use of static methods seems like an easier solution, but in the long run, code written like this becomes problematic. It's simple, clear and easy, but when you want to write unit test there is a problem. You are not able to write correct unit test for it.

What's the correct unit test ?

  • it has to test unit of work
  • contains only one assert
  • most important it is self contained

With static methods like Console.ReadKey and Console.Write you can't do it. These methods are dependant on the Windows Console implementation. We can't control this behaviour, we don't know exactly how it works inside. What we can do is to get rid of them.

Getting rid of static method

In this approach, I will show you how to hide "static" methods behind a layer of abstraction. Instead of using Console methods directly, let us inject some class that uses Console.

New Method

public static string GetMaskedInput(IConsoleWrapper consoleWrapper)
{
    string pwd = "";
    ConsoleKeyInfo key;
    do
    {
        key = consoleWrapper.ReadKey(true);
        if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter)
        {
            pwd += key.KeyChar;
            consoleWrapper.Write("*");
        }
        else
        {
            if (key.Key == ConsoleKey.Backspace && pwd.Length > 0)
            {
                pwd = pwd.Substring(0, pwd.Length - 1);
                consoleWrapper.Write("\b \b");
            }
        }
    }
    while (key.Key != ConsoleKey.Enter);
    return pwd;
}

Interface definition

public interface IConsoleWrapper
{
    ConsoleKeyInfo ReadKey();
    void Write(string data);
}

The sample method now have one parameter. This parameter is an interface IConsoleWrapper that has two methods. Their name is similar to the methods provided by the console Class. New code now calls the IConsoleWrapper interface. It doesn't need to know implementation details.

Interface implementation

public class ConsoleWrapper : IConsoleWrapper
{
    public ConsoleKeyInfo ReadKey()
    {
        return Console.ReadKey(true);
    }

    public void Write(string data)
    {
        Console.Write(data);
    }
}

The implementation of Console is now hidden. It was done by wrapping the static methods inside a class that can now be injected to the GetMaskedInput method.

We are able now to write unit test for the code.

Unit Test Example with Stub

In order to test this code, we can create a stub that implements the IConsoleWrapper interface. This stub will just simulate Console. We can control it's behaviour and thus create a stable test scenarios.

Test Stub

public class ConsoleWrapperStub : IConsoleWrapper
{
    private IList keyCollection;
    private int keyIndex = 0;

    public ConsoleWrapperStub(IList keyCollection)
    {
        this.keyCollection = keyCollection;
    }

    public string Output = string.Empty;

    public ConsoleKeyInfo ReadKey()
    {
        var result = keyCollection[this.keyIndex];
        keyIndex++;
        return new ConsoleKeyInfo( (char)result ,result ,false ,false ,false);
    }

    public void Write(string data)
    {	
        Output += data;
    }
}

This stub simulates Write method by maintaing the Output variable. It's a string and calling Write now appends this Output. We can now check the Output easily in the unit test.

ReadKey method is simulated by returning predefined data provided through a stub constructor. This behaviour is similar to replaying a recorded message. For each test a new stub is created with predefined key collection. Each call to ReadKey returns next key from the collection.

Test

 [Test]
    public void If_two_chars_return_pass_and_output_coded_pass()
    {
        // Arrange
        var stub = new ConsoleWrapperStub(new List
            { ConsoleKey.A, ConsoleKey.B, ConsoleKey.Enter });
        var expectedResult = "AB";
        var expectedConsoleOutput = "**";

        // Act

        var actualResult = Program.GetMaskedInput(string.Empty, stub);

        //Assert     
        Assert.That(actualResult, Is.EqualTo(expectedResult));
        Assert.That(stub.Output, Is.EqualTo(expectedConsoleOutput));
    }

This test will simulate a scenario with user clicking A,B,Enter keys. Enter should finish the procedure. We are expecting to see two "*" chars in the output and "AB" should be returned. Test has two asserts so it's not a perfect example, but this particular logic required us to do this.

Summary

  • static methods are evil get rid of them if you want to write unit test
  • do this by hiding them behind a layer of abstraction
  • write unit tests by mocking this layer or create a stub that simulates behaviour needed for the test
Share