Grabbing the output sent to Console.Out from within a unit test?
Asked Answered
P

3

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?

Parameter answered 26/1, 2010 at 12:19 Comment(2)
I agree, I'm reworking the solution layout to reflect that right now.Parameter
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.Parameter
A
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.

Aground answered 26/1, 2010 at 12:27 Comment(12)
If you use Resharper you will lose output screen for all further tests by doing this :(Paris
@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#.Lebbie
@Abel, did you mean OpenStandardError or OpenStandardOutput?Xavier
I have tried this and I have not seemed to need Console.SetOut(new StreamWriter(Console.OpenStandardError())Xavier
@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).Lebbie
@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.Xavier
I have asked a follow up question here: #29268411Xavier
Appears the blog entry has disappeared.Erythro
@Lebbie 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.Slipper
@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.Lebbie
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 strangeSlipperwort
@Lombas, ConsoleUser is a stand-in for the class that uses System.Console - your System Under Test (SUT).Aground
H
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.

Holston answered 15/11, 2012 at 11:56 Comment(3)
You haven't read the self-promotion FAQ. Every single answer you have posted has been a link to your blog.Arrangement
Andrew I think this answer may fall under the acceptabe criteria listed here meta.stackexchange.com/questions/94022/… 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 :) meta.stackexchange.com/questions/7931/…Ceuta
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.Slipper
C
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!");
Campground answered 15/2, 2023 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.