60

I am building a unit test in C# with NUnit, and I'd like to test that the main program actually outputs the right output depending on the command line arguments.

Is there a way from an NUnit test method that calls Program.Main(...) to grab everything written to Console.Out and Console.Error so that I can verify against it?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • I agree, I'm reworking the solution layout to reflect that right now. – Lasse V. Karlsen Jan 26 '10 at 12:33
  • Though it is in sort of a gray area, I am not actually invoking any external program, just calling code in my program file, but I still think it is more like an integration test than a unit test. – Lasse V. Karlsen Jan 26 '10 at 12:34

3 Answers3

93

You can redirect Console.In, Console.Out and Console.Error to custom StringWriters, like this

[TestMethod]
public void ValidateConsoleOutput()
{
    using (StringWriter sw = new StringWriter())
    {
        Console.SetOut(sw);

        ConsoleUser cu = new ConsoleUser();
        cu.DoWork();

        string expected = string.Format("Ploeh{0}", Environment.NewLine);
        Assert.AreEqual<string>(expected, sw.ToString());
    }
}

See this blog post for full details.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • If you use Resharper you will lose output screen for all further tests by doing this :( – Egor Pavlikhin May 12 '10 at 07:32
  • 4
    @EgorPavlikhin: you should reset the stdout at the end of each test by using `Console.SetOut(new StreamWriter(Console.OpenStandardError())` (you may need to also set `Autoflush` to true). After this, it will work with any test runner, including R#. – Abel Oct 02 '14 at 00:24
  • 1
    @Abel, did you mean OpenStandardError or OpenStandardOutput? – Daryn Mar 21 '15 at 18:50
  • I have tried this and I have not seemed to need Console.SetOut(new StreamWriter(Console.OpenStandardError()) – Daryn Mar 21 '15 at 18:51
  • @Daryn: as in my opening line, you can use this for `In`, `Out` and `Error`, or in other words, `stdin`, `stdout`, and `stderr`. If you do not use `Console.SetOut`, you will not be able to catch the output in a string, as there is no way to get it from the `Console` class directly. In your last comment, you use a `new StreamWriter...` inside `Console.SetOut`, which will have no effect (you cannot access it) and will leak memory (use the `using` statement). – Abel Mar 24 '15 at 11:18
  • @Abel, I was referring to your quote here on SO on Oct 2'14. You said "you should reset the stdout at the end of each test by using `Console.SetOut(new StreamWriter(Console.OpenStandardError())`". This has `SetOut` followed by `OpenStandardError`. I was asking if you meant `OpenStandardOutput` or if you really meant `OpenStandardError`. – Daryn Mar 25 '15 at 22:14
  • I have asked a follow up question here: http://stackoverflow.com/questions/29267911/redirecting-console-out-within-test-setup-and-teardown – Daryn Mar 25 '15 at 22:47
  • Appears the blog entry has disappeared. – youeee Mar 23 '19 at 10:18
  • 1
    @Abel I find that if I run each test in a series of tests *separately* the output is correct, but if I run *all* the tests in a row, then only the first test succeeds. The others fail because there's nothing in the StreamWriter after the first test. And yet I reset the standard output after each test using your method (Console.SetOut). I don't understand. – jeancallisti Sep 08 '21 at 20:14
  • @jeancallisti, sounds to me something is not cleaned up properly, or the test driver you use wreaks havoc on your streams. You may want to investigate why you're not getting a handle to stdout. Another cause may be that you're running them in parallel perhaps. – Abel Oct 18 '21 at 16:53
  • Visual Studio with .NET 6.0, did not find a ConsoleUser class and I also did not find any result while googling..... Maybe it's available in .NET Framework, but no documentation online is very strange – Lombas Dec 29 '22 at 20:57
  • 1
    @Lombas, `ConsoleUser` is a stand-in for the class that uses `System.Console` - your System Under Test (SUT). – Mark Seemann Dec 30 '22 at 17:57
23

You can use this simple class to get the output with a using statement:

public class ConsoleOutput : IDisposable
{
    private StringWriter stringWriter;
    private TextWriter originalOutput;

    public ConsoleOutput()
    {
        stringWriter = new StringWriter();
        originalOutput = Console.Out;
        Console.SetOut(stringWriter);
    }

    public string GetOuput()
    {
        return stringWriter.ToString();
    }

    public void Dispose()
    {
        Console.SetOut(originalOutput);
        stringWriter.Dispose();
    }
}

Here is an example how to use it:

using (var consoleOutput = new ConsoleOutput())
{
    target.WriteToConsole(text);

    Assert.AreEqual(text, consoleOutput.GetOuput());
}

you can find more detailed information and a working code sample on my blog post here - Getting console output within a unit test.

Vasil Trifonov
  • 1,857
  • 16
  • 21
  • 4
    You haven't read the self-promotion FAQ. Every single answer you have posted has been a link to your blog. – Andrew Barber Nov 16 '12 at 07:10
  • 3
    Andrew I think this answer may fall under the acceptabe criteria listed here https://meta.stackexchange.com/questions/94022/how-can-i-link-to-an-external-resource-in-a-community-friendly-way Can also suggest that starting with "You have not read" is less friendly than it could be. You could suggest they read the FAQs with a link :) https://meta.stackexchange.com/questions/7931/faq-for-stack-exchange-sites – Dan Walmsley Sep 16 '20 at 23:24
  • I am using this very class and I find that if I run each test separately the output is correct, but if I run all the tests in a row, then only the first test succeeds. The other tests fail because there's nothing in the GetOutput. As if the standard output was not properly reset after the first test, despite variable "originalOutput" which is there exactly for that reason. – jeancallisti Sep 08 '21 at 20:13
1

You still need an integration test, not unit, as others have correctly suggested.

Integration test

ProgramTest.cs

using NUnit.Framework;

class ConsoleTests
{
    [Test]
    public void TestsStdOut()
    {
        var capturedStdOut = CapturedStdOut(() =>
        {
            RunApp();
        });

        Assert.AreEqual("Welcome, John!", capturedStdOut);
    }

    void RunApp(string[]? arguments = default)
    {
        var entryPoint = typeof(Program).Assembly.EntryPoint!;
        entryPoint.Invoke(null, new object[] { arguments ?? Array.Empty<string>() });
    }

    string CapturedStdOut(Action callback)
    {
        TextWriter originalStdOut = Console.Out;

        using var newStdOut = new StringWriter();
        Console.SetOut(newStdOut);

        callback.Invoke();
        var capturedOutput = newStdOut.ToString();

        Console.SetOut(originalStdOut);

        return capturedOutput;
    }
}

Implementation

Program.cs

Console.Write($"Welcome, John!");
Artur INTECH
  • 6,024
  • 2
  • 37
  • 34