18

Is there a standard way of making a C# console application unit-testable by programming against an interface, rather than System.Console?

For example, using an IConsole interface?

Have you done this, and what kind of methods did you use?

Did you expose events for when your application needs to write to the standard output?

Jonathan
  • 32,202
  • 38
  • 137
  • 208
  • possible duplicate of [Can this be mocked with Moq?](http://stackoverflow.com/questions/1220454/can-this-be-mocked-with-moq) – Jonathan May 02 '11 at 02:05
  • https://msdn.microsoft.com/en-us/magazine/mt833289.aspx | Microsoft is working on an open source package that will handle this (it's currently published as alpha for now in nuget.org, but that will change sooner than later I hope). – myermian Jun 21 '19 at 16:46

5 Answers5

17

I think your approach with an interface would work, and I don't think I would make use of events. Assuming the application does not accept user input other than command line parameters, I would probably use something like this to wrap Console.Write/Console.WriteLine:

public interface IConsoleWriter
{
    void Write(string format, params object[] args);
    void WriteLine(string format, params object[] args);
}

To test, I would either create a TestConsoleWriter that would store all writes into a buffer that I could then assert against, or I would create a mock and verify that Write or WriteLine was called with the parameters I expected. If your application is going to be doing a ton of writing to the console (say +100 MB or so of output), then using a mock would probably be preferable for performance reasons, but otherwise I would say pick whichever method you think would be easier to work with.

This approach does have a few limitations, however. If you are using any assemblies that you cannot modify and they write to the console, then you won't see that output since you can't force those classes to use your IConsoleWriter. Another problem is that the Write and WriteLine methods have 18 or so overloads, so you may be wrapping alot of methods. To get around these limitations, you may just want to use the Console.SetOut method to redirect the console output to your own TextWriter while testing.

Personally, I think I would take the SetOut approach. It would just be one line you have to add at the beginning of your unit tests (or possibly in a SetUp method) and you can just assert against what is written into the TextWriter.

rsbarro
  • 27,021
  • 9
  • 71
  • 75
  • Great answers from everyone else, but I favour this one, because it effectively isolates the program from concrete 'Console' instance, which allows me to write a pure unit test. The other suggestions are better suited to behaviour/integration testing. – Jonathan May 02 '11 at 03:41
  • 2
    I didn't think of the `SetOut` approach. Neat. – corlettk May 02 '11 at 04:18
  • Problem with modifying the console behaviour using setout/setin is how this interacts with a test framework which uses the console itself. – annakata Mar 05 '14 at 11:44
  • 1
    There is just one little thing with the Console.SetOut approach - console is a static class. Hence, when running tests in parallel, the output will be messed up. Like @rsbarro mentioned, Console.WriteLine has lots of override, but usually only few of them are really used - hence, the method definitions could be added to the IConsoleWriter one by one once needed. – Max Apr 30 '18 at 19:18
  • @annakata xUnit is pretty smart, as it does not use the Console at all. An instance of `ITestOutputHelper` can be injected in the test constructor, which is used just like a Console, while not interfering with the real Console. – Arialdo Martini Sep 20 '18 at 04:23
9

You want to change the stream that console writes to in your unit tests. Then you can put in a mock stream or whatever. Check this post by Mark Seemann on testing Console:

http://blogs.msdn.com/b/ploeh/archive/2006/10/21/consoleunittesting.aspx

xofz
  • 5,600
  • 6
  • 45
  • 63
4

I happened to trip over this thread last night: Using reflection to override virtual method tables in C#.

@paulo came up with the answer: LinFu by Philip Laureano.

The usage example on Philip Laureano's Development Blog: Intercepting Console.WriteLine guides you through an example of how to intercept calls to the Console.WriteLine method, and (in this case) perform some additional operations as well... AOP for .NET... Very Noice, IMHO!

LinFu might be just the ticket, as it has no presence within the thing being intercepted, so you can "intercept and modify" the behaviour of calls to third-party-vendor's assemblies "within your context", but without any possibility of affecting the ACTUAL behaviour of the intercepted classes outside of that context... so LinFu sounds like a darn good place to start implementing a "generic testing-framework for console apps".

You MAY even be able to leverage one of the existing unit-testing frameworks as well. I'd be looking for an open-source framework. NUnit springs to mind. Pun intended.

Good luck with it anyway, it's an interesting project. Keep us posted, K?

Cheers. Keith.

Community
  • 1
  • 1
corlettk
  • 13,288
  • 7
  • 38
  • 52
  • Thanks for your input! Application I've been working on is here: https://bitbucket.org/jonathanconway/marsrover – Jonathan May 02 '11 at 03:42
2

I recommend using moles.

Mainly because I prefer letting your design determine your interfaces and classes, rather than your testing.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Very interesting idea. If I had more time I might try it. It sounds like a good approach, since I'm *replacing* methods. – Jonathan May 02 '11 at 03:45
  • +1 for mentioning Moles. I love PEX btw, it is also a great thing to at least play with and keep in mind for later. – quetzalcoatl Feb 27 '12 at 09:26
1

This code will work if you use just one thread:

[TestClass]
public class MyTests
{
    private StringBuilder output;
    private StringWriter tempOutputWriter;
    private TextWriter originalOutputWriter;

    [TestInitialize]
    public void InitializeTest()
    {
        this.originalOutputWriter = Console.Out;
        this.tempOutputWriter = new StringWriter();
        Console.SetOut(tempOutputWriter);
        this.output = tempOutputWriter.GetStringBuilder();
    }

    [TestCleanup]
    public void CleanupTest()
    {
        Console.SetOut(originalOutputWriter);
        this.tempOutputWriter.Dispose();
    }

    [TestMethod]
    public void Test1()
    {
        Program.Main(new string[] { "1", "2", "3" });
        string output = this.output.ToString();
        ...
        this.output.Clear();
    }

    [TestMethod]
    public void Test2()
    {
        Program.Main(new string[] { "4", "5", "6" });
        string output = this.output.ToString();
        ...
        this.output.Clear();
    }
}
polina-c
  • 6,245
  • 5
  • 25
  • 36