views:

386

answers:

6

I'm trying to write a C# unit test with VS 2008's built-in unit testing framework and the method I'm testing calls Environment.Exit(0). When I call this method in my unit test, my unit test is Aborted. The method should indeed be calling Exit, and I want a way to test that it does, and also to test the exit code that it uses. How might I do this? I looked at Microsoft.VisualStudio.TestTools.UnitTesting Namespace but didn't see anything that looked relevant.

[TestMethod]
[DeploymentItem("myprog.exe")]
public void MyProgTest()
{
    // Want to ensure this Exit's with code 0:
    MyProg_Accessor.myMethod();
}

Meanwhile, here's the gist of the code that I want to test:

static void myMethod()
{
    Environment.Exit(0);
}

Edit: here's the solution I used in my test method, thanks to RichardOD:

Process proc;

try
{
    proc = Process.Start(path, myArgs);
}
catch (System.ComponentModel.Win32Exception ex)
{
    proc = null;
    Assert.Fail(ex.Message);
}

Assert.IsNotNull(proc);
proc.WaitForExit(10000);
Assert.IsTrue(proc.HasExited);
Assert.AreEqual(code, proc.ExitCode);
+3  A: 

You won't be able to test this - Environment.Exit kills the application completely. This means that any AppDomain that uses this code will be unloaded completely, whether it is your production application or the unit testing framework.

Andrew Hare
+4  A: 

This sounds like an incredibly bad idea. Environment.Exit(0), will obviously do as prescribed, hence why your unit testings are breaking.

If you really want to still test this you can by launching a seperate process and checking the return code- have a look at wrapping it up in Process.Start.

I guess another option is factoring this code out and injecting a test spy, or using a mock object to verify correct behaviour.

Perhaps you can do something with Typemock Isolator- I believe this lets you mock static methods.

RichardOD
+1 on TypeMock Isolator here - it's the only solution I'm aware of which lets you intercept and mock absolutely anything.
Pavel Minaev
The danger in being able to mock static methods is that you don't wean yourself off of using them as easily. I actually find that (for my code) being forced to work extra hard to have static methods is a good thing since I won't use them unless they are the absolute best solution. This helps to force development using better techniques, IMO. The downside is that you are forced to jump through hoops when interacting with those static methods when they do make sense (or the framework isn't built with testing in mind).
tvanfosson
@tvanfosson- That's a good point. That's why many people (myself included) try to avoid overusing static methods when writing testable code. .NET framework classes that are static are a pain test and developers often have to resort to writing wrapper code to make it testable (as per your answer). You can see this a lot from the evolution of ASP.NET Web forms to ASP.NET MVC.
RichardOD
+2  A: 

Your only option here would be to mock the Environment class with a fakie Exit method.

Spencer Ruport
+1  A: 

You'll need to create a wrapper for the Environment class, then use the wrapper in your code. For your unit tests, inject a mock version of the wrapper. The following example uses RhinoMocks to verify that the method calls the wrapper with the expected argument.

public class EnvironmentWrapper
{
    public virtual void Exit( int code )
    {
        Environment.Exit( code );
    }
}


public class MyClass
{
    private EnvironmentWrapper Environment { get; set; }

    public MyClass() : this( null ) { }

    public MyClass( EnvironmentWrapper wrapper )
    {
        this.Environment = wrapper ?? new EnvironmentWrapper();
    }

    public void MyMethod( int code )
    {
        this.Environment.Exit( code )
    }
}


[TestMethod]
public void MyMethodTest()
{
     var mockWrapper = MockRepository.GenerateMock<EnvironmentWrapper>();

     int expectedCode = 5;

     mockWrapper.Expect( m => m.Exit( expectedCode ) );

     var myClass = new MyClass( mockWrapper );

     myclass.MyMethod( expectedCode );

     mockWrapper.VerifyAllExpectations()
}
tvanfosson
+1. Nice example, if I wasn't eating my dinner I was going to code something along those lines! Your code reminds me of the default code in ASP.NET MVC projects AccountController class- which can only be a good thing. Personally I would change public EnvironmentWrapper Environment { get; set; } to a private set, but apart from that nice example.
RichardOD
@RichardOD -- agreed on the public/private. Will update.
tvanfosson
A: 

You can add an argument to your method to pass it a fake environment where the exit() method won't exit.

You can this parametrized method extracted from the method called from your application, and unit test the extracted function. That way, you won't have to modify your app.

philippe
A: 

The only thing that comes to my mind is something along:

static void myMethod()
{
    DoEnvironmentExit(0);
}

static void DoEnvironentExit(int code)
{
    #if defined TEST_SOLUTION
      SomeMockingFunction(code);
    #else
      Environment.Exit(code);
    #endif
}
Daniel Daranas