4

Is there a way I can check what is the output to the console in my unit test of my abstract class Question?

I am using NUnit & Moq.

My unit test looks like this:

    [Test]
    public void QuestionAsk()
    {
        var mock = new Mock<Question>(new object[]{"question text",true});

        mock.CallBase = true;

        var Question = mock.Object;

        Question.Ask();

        mock.Verify(m => m.Ask(), Times.Exactly(1));

    }

Here I am checking that Question.Ask() is called and it works fine. Ask() does not return a value so I cannot assign it to a variable. The function just outputs to the console.

Is there a way I can verify in the test that the output == "question text" ?

EDIT: Forgot to mention Question is an abstract base class.

I tried the Concole.Setout method suggested with this code:

    [Test]
    public void QuestionAsk()
    {
        var mock = new Mock<Question>(new object[]{"question text",true});

        mock.CallBase = true;

        var Question = mock.Object;

        using (var consoleText = new StringWriter())
        {
            Console.SetOut(consoleText);
            Question.Ask();
            Assert.That(consoleText.ToString(), Is.StringMatching("question text"));
        }
        mock.Verify(m => m.Ask(), Times.Exactly(1));

    }

But it took 236 ms which is far too long for a test. Implementing the IWriter interface seems the best way to deal with it so I will give that a go now.

Guerrilla
  • 13,375
  • 31
  • 109
  • 210
  • 1
    See this: http://stackoverflow.com/questions/5852862/mocking-system-console-behaviour. – Keith Payne Dec 15 '13 at 14:04
  • Besides @Keith's link, you don't actually want to call console from your Question "business object". It's the running program's task to perform the output, because you now cannot reuse this class for, say, a web application. If you just let it return the string, the calling program can decide how to output it. – CodeCaster Dec 15 '13 at 14:08
  • @CodeCaster Good point. I will be eventually porting this to web so I should take care of that sooner rather than later. I am doing this project to learn C#. I really appreciate the advice. – Guerrilla Dec 15 '13 at 14:48

2 Answers2

5

You can initialize Question with a custom output writer, and then mock the writer to verify the output:

public interface IOutputWriter 
{
    void WriteLine(string s);
}

// Use this console writer for your live code
public class ConsoleOutputWriter : IOutputWriter
{
    public void WriteLine(string s)
    {
        Console.WriteLine(s);
    }
}

public abstract class Question
{
    private readonly IOutputWriter _writer;
    private readonly string _text;
    private readonly bool _default;

    public Question(IOutputWriter writer, params object[] args)
    {
        _writer = writer;
        _text = (string)args[0];
        _default = (bool)args[1];
    }

    public void Ask()
    {
        _writer.WriteLine(_text);
    }
}


[Test]
public void QuestionAsk()
    {
        var writer = new Mock<IOutputWriter>();

        var mock = new Mock<Question>(writer.Object, new object[]{"question text",true});

        mock.CallBase = true;

        var Question = mock.Object;

        Question.Ask();

        mock.Verify(m => m.Ask(), Times.Exactly(1));
        mock.Verify(w => w.WriteLine(It.Is<string>(s => s == "question text")), Times.Once)

    }
Keith Payne
  • 3,002
  • 16
  • 30
  • Thanks this seems the best way to do it. I initiatlly liked the Console.SetOut method as it just needs a single line of code to my existing tests but it made my test go from 6ms to 236ms. This would cripple me as I plan on having lots of tests. Is this performance decrease normal with SetOut or could I have done something wrong? Is it ok to paste my code in as an answer or do I have to make a new question for that? Sorry I am still new here – Guerrilla Dec 15 '13 at 14:43
  • You can post an answer to your own question. – Keith Payne Dec 15 '13 at 15:08
  • I suspect the last line `mock.Verify(w => w.WriteLine(It.Is(s => s == "question text")), Times.Once)` should be `writer.Verify(w => w.WriteLine(It.Is(s => s == "question text")), Times.Once)`. Another issue, `Ask` in the `Question` class should be `virtual`. – Daryn Feb 07 '15 at 14:59
3

Your test looks very strange - you are exercising mocked object instead of testing some real object which will be used by your application. If you are testing Question object, then you should use exactly same instance of Question type, as your application would use. What should be mocked - dependencies of Question. Because classes should be unit-tested in isolation (otherwise problems with dependencies would result in failing tests of Question which works fine).

So, if you have Question which displays something on console (i.e. it depends on Console), then unit-testing requires mocking of this dependency. You can't mock Console with Moq, because it's static class. Thus you should create abstraction for console, which will be used by Question:

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

Now inject this dependency to your Question:

public class Question
{
    private IConsole _console;
    private string _message;

    public class Question(IConsole console, string message)
    {
        _console = console;
    }
}

With this code you can write tests for Question behavior:

[Test]
public void ShouldAskQuestionOnConsole()
{
    var message = "Hello World";
    var consoleMock = new Mock<IConsole>();
    consoleMock.Setup(c => c.Write(message));
    var question = new Question(consoleMock.Object, message);

    question.Ask();     

    consoleMock.VerifyAll();
}

This test specifies, that question should send its message to console, when Ask is executed. Implementation is simple:

public void Ask()
{
    _console.Write(_message);
}

Now you have working Question. And you should implement IConsole which will be used by your application. It's simple wrapper:

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

In your real application inject this implementation to question (this will can be done automatically by dependency injection framework):

IConsole console = new ConsoleWrapper();
Question question = new Question(console, message);

Notes: I'd go with some interface like IView instead of IConsole. That will completely abstract Question class from type of UI it works with. And second note is separating of business logic from presentation logic. Usually you don't have class which is responsible for two things - holding data of question (and possibly processing it), and interacting with user. Usually there is something like controller, which receives user input, refreshes UI and asks business model for data.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459