I have a Test Project in Visual Studio I'd like to use to test my console application with (in the same solution).
I'm trying to set up tests which call the console app with specific parameters, and compare the actual output with what I expect, then do my usual Assert statements to appropriately pass/fail the test.
The best way to do this, that I can come up with, is to execute the app exe with System.Diagnostics.Process inside the unit test. This works. I can read the output, and all is well.
The problem I'm having is when I want to set a breakpoint inside the console app code, so I can do some debugging. Since Process kicked off the console app, Visual Studio isn't watching the console app, so it won't break. There's nothing like "Wait for a request from an external application" like there is with Web Apps, and I understand why, but that's basically what I'm looking for.
So my question is, is there any way to set up these unit tests inside Visual Studio, where I can still debug the console app? The only workaround I can think of is setting the Start Action on the console application to start an external program, which would call MSTest.exe, and run the appropriate unit tests that way. But this seems like a problem which I'm just thinking about it wrong, and there actually is a much more obvious solution.
Make your console application as thin as possible, and move all business logic to domain classes. E.g.
class Program
{
static void Main(string[] args)
{
Foo foo = new Foo(args);
}
}
After that you can easily write unit tests for your Foo class.
Unit test shouldn't require human interaction. To make your application unit-testable, console interaction should be abstracted - this can be easily done using TextReader
and TextWriter
classes. You might find this question helpful.
There are a lot of answers in the present question, as well as in Best way to unit test console c# app, NUnit Test - Looping - C#, and possibly many others, indicating that direct unit testing of the unmodified, "untestable" console application is not a good way to test. They are all correct.
However, if you really need to test this way for some reason, and if you are able to refer to the console application as a reference from your test project (which, if the two are in the same solution, you likely can), it is possible to do so without resorting to Process.Start
. In .NET 4.5 or later, using xunit syntax:
[Theory]
[MemberData("YourStaticDataProviderField")]
public async void SomeTest(string initialString, string resultString, params int[] indexes)
{
using (var consoleInStream = new AnonymousPipeServerStream(PipeDirection.Out))
using (var consoleOutStream = new AnonymousPipeServerStream(PipeDirection.In))
using (var writer = new StreamWriter(consoleInStream, Encoding.Default, 1024, true))
using (var reader = new StreamReader(consoleOutStream, Encoding.Default, false, 1024, true))
using (var tokenSource = new CancellationTokenSource())
{
// AutoFlush must be set to true to emulate actual console behavior,
// else calls to Console.In.Read*() may hang waiting for input.
writer.AutoFlush = true;
Task programTask = Task.Run(() =>
{
using (var consoleInReader =
new StreamReader(new AnonymousPipeClientStream(PipeDirection.In,
consoleInStream.GetClientHandleAsString())))
using (var consoleOutWriter =
new StreamWriter(new AnonymousPipeClientStream(PipeDirection.Out,
consoleOutStream.GetClientHandleAsString())))
{
// Again, AutoFlush must be true
consoleOutWriter.AutoFlush = true;
Console.SetIn(consoleInReader);
Console.SetOut(consoleOutWriter);
// Of course, pass any arguments your console application
// needs to run your test. Assuming no arguments are
// needed:
Program.Main(new string[0]);
}
}, tokenSource.Token);
// Read and write as your test dictates.
await writer.WriteLineAsync(initialString.Length.ToString());
await writer.WriteLineAsync(initialString);
await writer.WriteLineAsync(indexes.Length.ToString());
await writer.WriteLineAsync(String.Join(" ", indexes));
var result = await reader.ReadLineAsync();
await writer.WriteLineAsync();
// It is probably a good idea to set a timeout in case
// the method under test does not behave as expected (e.g.,
// is still waiting for input). Adjust 5000 milliseconds
// to your liking.
if (!programTask.Wait(5000, tokenSource.Token))
{
tokenSource.Cancel();
Assert.False(true, "programTask did not complete");
}
// Assert whatever your test requires.
Assert.Null(programTask.Exception);
Assert.Equal(resultString, result);
}
}
This solution is likely adaptable to .NET 3.5 or later if you handle asynchronous methods differently. AnonymousPipe(Server|Client)Stream
was
introduced in .NET 3.5. Other unit test frameworks should work with the
appropriate syntax changes.
The pipe streams System.IO.Pipes.AnonymousPipeServerStream
and System.IO.Pipes.AnonymousPipeClientStream
are key to making this solution work. Because a stream has a current position, it does not work as reliably to have two different processes referring to the same MemoryStream
at the same time. Using the pipe streams instead allows the streams' use in parent and child processes, as is done here. Running Program.Main(string[])
in a child task is necessary so that the unit test process can read and write from the console while the program is running. The AnonymousPipeClientStream
objects should belong to the child task, according to the documentation, which is why they are created within the task runner.
You can obtain exception data from the programTask
object if you need to test for exceptions (or, under xunit, use something like Assert.ThrowsAsync<ExpectedException>(Func<Task>)
to run the child task).