Tuesday, June 17, 2008

Simple dynamic compiling to mock a Console Application

Toby has written a follow up to ‘Spawning a console application and tracking its Standard Output’ found here.

This article deals with unit testing an application that relies on interaction with a console application.

The problem is making the console application behave in a specific manner for the unit tests. For instance, simulating network issues, file access issues, strange multiple file issues and so on can be a right pain in the neck. Throw in other factors such as the speed of the unit tests with a large console application running, trying to automate the setting up of an entire staged environment and invoking a complex, production only and sometimes cranky console application makes this a challenge I'm keen to avoid.

A simpler approach is to mock the external console application, so that the mocked application returns exactly what you need to test the behaviour of your own application.

We have a few options to solve this, along with the negatives of the approach:
  • Making a simple batch file to accept input, and spit out the correct text on demand
    - Limited interaction potential, validation etc
    - No access to .NET goodness
  • Writing a bunch of .NET console apps which are compiled as part of the solution
    - Too many projects to maintain
    - Hard coded and not flexible for different environments
  • Dynamically generate console apps on the fly with environmental changes as needed
    - Sounds like a solution to me !!!

Writing the mocked console application

The following test code sample shows messages being written to Standard Output and files being created. You will need a separate mock application for each test that you need to perform.

namespace TestApplication
{
   class Program
   {
      static void Main(string[] args)
      {
         Console.WriteLine(@"Connected to www.externalserver.com made.");
         // put download files here
         StreamWriter sw = new StreamWriter(@"C:\DownloadFiles\File1");
         Console.WriteLine("Downloading file File1");
         sw.Write("Text Content of File 1");
         sw.Close();

         sw = new StreamWriter(@"C:\DownloadFiles\File2");
         Console.WriteLine("Downloading file File2");
         sw.Write("Text Content of File 2");
         sw.Close();

         Console.WriteLine(@"Closed Connection.");
      }
   }
}


Dynamically generating mock console applications on the fly

The source code for each test application is included in my solution as a string resource called TestScript. The source code is then compiled on the fly to an executable file. The code to compile the resource is:

Microsoft.CSharp.CSharpCodeProvider cscp = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.CompilerParameters param = new System.CodeDom.Compiler.CompilerParameters();
param.GenerateExecutable = true;
param.OutputAssembly = @"c:\App.EXE";

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    param.ReferencedAssemblies.Add(assembly.Location);
}
System.CodeDom.Compiler.CompilerResults results = cscp.CompileAssemblyFromSource(param, TestScript.Test1);


Because this works from text, there is also the opportunity to use string.Replace to manage paths specific to the testing environment.

Setting param.GenerateExecutable to true causes an executable to be generated instead of a dll. Put the file name to generate to in param.OutputAssembly.

The above code assumes that the assemblies required to be referenced are the same as the currently executing application. This is achieved via the for loop. This can be altered by adding specific assemblies to param.ReferencedAssemblies in addition to, or instead of this loop.

If you are in an experimenting mood, any compiler directives available via the compiler options in Visual Studio are available to you. Have a play with System.CodeDom.Compiler.CompilerParameters.CompilerOptions;

No comments: