Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# unit test for a method which calls Console.ReadLine()

I want to create a unit test for a member function of a class called ScoreBoard which is storing the top five players in a game.

The problem is that the method I created a test for (SignInScoreBoard) is calling Console.ReadLine() so the user can type their name:

public void SignInScoreBoard(int steps)
{
    if (topScored.Count < 5)
    {
        Console.Write(ASK_FOR_NAME_MESSAGE);
        string name = Console.ReadLine();
        KeyValuePair<string, int> pair = new KeyValuePair<string, int>(name, steps);
        topScored.Insert(topScored.Count, pair);
    }
    else
    {
        if (steps < topScored[4].Value)
        {
            topScored.RemoveAt(4);
            Console.Write(ASK_FOR_NAME_MESSAGE);
            string name = Console.ReadLine();
            topScored.Insert(4, new KeyValuePair<string, int>(name, steps));
        }
    }
}

Is there a way to insert like ten users so I can check if the five with less moves (steps) are being stored?

like image 577
Petur Subev Avatar asked Jul 01 '10 20:07

Petur Subev


2 Answers

You'll need to refactor the lines of code that call Console.ReadLine into a separate object, so you can stub it out with your own implementation in your tests.

As a quick example, you could just make a class like so:

public class ConsoleNameRetriever {
     public virtual string GetNextName()
     {
         return Console.ReadLine();
     }
}

Then, in your method, refactor it to take an instance of this class instead. However, at test time, you could override this with a test implementation:

public class TestNameRetriever : ConsoleNameRetriever {
     // This should give you the idea...
     private string[] names = new string[] { "Foo", "Foo2", ... };
     private int index = 0;
     public override string GetNextName()
     {
         return names[index++];
     }
}

When you test, swap out the implementation with a test implementation.

Granted, I'd personally use a framework to make this easier, and use a clean interface instead of these implementations, but hopefully the above is enough to give you the right idea...

like image 118
Reed Copsey Avatar answered Oct 02 '22 19:10

Reed Copsey


You should refactor your code to remove the dependency on the console from this code.

For instance, you could do this:

public interface IConsole
{
    void Write(string message);
    void WriteLine(string message);
    string ReadLine();
}

and then change your code like this:

public void SignInScoreBoard(int steps, IConsole console)
{
    ... just replace all references to Console with console
}

To run it in production, pass it an instance of this class:

public class ConsoleWrapper : IConsole
{
    public void Write(string message)
    {
        Console.Write(message);
    }

    public void WriteLine(string message)
    {
        Console.WriteLine(message);
    }

    public string ReadLine()
    {
        return Console.ReadLine();
    }
}

However, at test-time, use this:

public class ConsoleWrapper : IConsole
{
    public List<String> LinesToRead = new List<String>();

    public void Write(string message)
    {
    }

    public void WriteLine(string message)
    {
    }

    public string ReadLine()
    {
        string result = LinesToRead[0];
        LinesToRead.RemoveAt(0);
        return result;
    }
}

This makes your code easier to test.

Of course, if you want to check that the correct output is written as well, you need to add code to the write methods to gather the output, so that you can assert on it in your test code.

like image 44
Lasse V. Karlsen Avatar answered Oct 02 '22 18:10

Lasse V. Karlsen