3

I have a lot of tests writen in this format:

[TestMethod]
public void TestMethod1()
{
    try
    {
        DoShomething();
    }
    catch (Exception e)
    {
        WriteExceptionLogWithScreenshot( e );
    }
}

[TestMethod]
public void TestMethod2()
{
    try
    {
        DoAnotherShomething();
    }
    catch ( Exception e )
    {
        WriteExceptionLogWithScreenshot( e );
    }
}

I would like to unify this exception handling using something like

[TestCleanup]
public void Cleanup()
{
    // find out if an exception was thrown and run WriteExceptionLogWithScreenshot( e )
}

Then I could avoid writing try catch blocks in all methods.

Does mstest support something like this? anyone have an ideia about what I could do?

Thiago
  • 1,103
  • 1
  • 16
  • 25

3 Answers3

5

Recently I had the same issue. And I found that if you are using MSTest V2 you can easily extend TestMethod Attribute class and use it instead like:

public class LoggedTestMethodAttribute : TestMethodAttribute
{
   public override TestResult[] Execute(ITestMethod testMethod)
   {
      var results = base.Execute(testMethod);
      //you can loop through results and call
      //WriteExceptionLogWithScreenshot(e);
   }
}

I guess when you don't use DataRow attribute then you will always have just one result in array. You can then directly get exception from result item's property TestFailureException

After that you just need to decorate your test methods with [LoggedTestMethod] attribute instead of [TestMethod]

jgasiorowski
  • 1,033
  • 10
  • 20
  • Thank you for adding this V2 answer! The answer was a creative solution within given constraints, but this should be the answer, now that V2 has come out. – JesusIsMyDriver.dll Feb 11 '20 at 15:15
4

While it is an easy and (thread) safe alternative to wrap the test code in a lambda like Kip's answer I've found that it makes the stack trace a little harder to read since you'll always have the anonomous labmda stuck in there. Also, it requires another nesting level and it just tickles my OCD nerves a little.

I'd love for this to be extensible in MSTest, and in something like XUnit or NUnit you can get around this. You could even do this Aspect Oriented by superimposing a wrapper method around each method being run using something like PostSharp (see here).

If you're stuck with MSTest for whatever reasons (we've got a few myself, "attaching" files, CI scripts having been setup etc) you could do this though.

Make use of the FirstChanceException which will catch any exception thrown. Now, what I propose here is not thread safe and a bit wacky but if you're like us, running single threaded tests and care more about ease of use than performance etc it might work for you, you could improve this by bundling the registrations etc with the test context.

Basically, what I did anyway is to have a global test class that hooks the FirstChanceExceptions and 'safekeeps' the last thrown exception, then I reference this in the cleanup method.

[TestClass]
public static class GlobalSetup
{
    [AssemblyInitialize]
    public static void Setup(TestContext context)
    {
        AppDomain.CurrentDomain.FirstChanceException += (s, e) => LastException = e.Exception;
    }

    public static Exception LastException { get; private set; }
}

Then in a base class which contains logic for cleaning up any test:

[TestCleanup]
public virtual void Cleanup()
{
    if (TestContext.CurrentTestOutcome != UnitTestOutcome.Passed && GlobalSetup.LastException != null)
    {
        var e = GlobalSetup.LastException;
        Log.Error(GlobalSetup.LastException, $"{e.GetType()}: {e.Message}\r\n{e.StackTrace}");
    }
}
Community
  • 1
  • 1
Almund
  • 5,695
  • 3
  • 31
  • 35
  • You're right, as brought up for example here: http://stackoverflow.com/a/5037514/479632 However I've not had any issues from that standpoint with the above solution since the tests are at least run sequentially. – Almund Sep 21 '16 at 04:10
  • sadly this doesn't work for me, after the test failure (and before the Cleanup) I get a '{System.IO.FileNotFoundException: Could not load the specified file. File name: 'Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.resources' at System.Runtime.Loader.AssemblyLoadContext.ResolveUsingEvent(AssemblyName assemblyName) at System.Runtime.Loader.AssemblyLoadContext.ResolveUsingResolvingEvent(IntPtr gchManagedAssemblyLoadContext, AssemblyName assemblyName)}' – Rob Jun 04 '18 at 07:00
  • That is good solution for sequential testing, but I get the Exception 2 times? Any Idea what could be wrong? Is it because of Assert? – Sebastian Mar 21 '19 at 12:33
  • @Sebastian is it logged two times you mean? Could be you have something else that's catching and logging exceptions? – Almund Mar 24 '19 at 19:37
  • @Rob I would wager a guess you've got issues with the test packages? If you remove the code I suggest does it work? Are you sure you're running MSTest? – Almund Mar 24 '19 at 19:38
  • I thought try catch Exceptions that are caught are not called by FirstChanceException, that was wrong. – Sebastian Mar 25 '19 at 13:33
  • I have made a Workaround, I check the Exception for Type AssertFailedException, maybe that's the only way. – Sebastian Mar 27 '19 at 06:54
3

This is simpler:

    private void TryTest(Action action)
    {
        try
        {
            action();
        }
        catch (Exception e)
        {
            WriteExceptionLogWithScreenshot(e);
            throw;
        }
    }

    [TestMethod]
    public void TestMethod1()
    {
        TryTest(new Action(() =>
        {
            DoSomething();
        }
        ));
    }

    [TestMethod]
    public void TestMethod2()
    {
        TryTest(new Action(() =>
        {
            DoAnotherSomething();
        }
        ));
    }

Be sure to re-throw the exception so the test fails. Notice the throw in the catch.

Kip Morgan
  • 728
  • 3
  • 12
  • 1
    Actually, if `DoSomething()` is a method you can simplify call to the `TryTest()` using method group: `TryTest(DoSomething)`; – Ivan Yurchenko Jun 06 '16 at 15:05
  • 1
    Although this solution works, I would prefer a solution built in mstest. Besides that my real methods receive parameters with different types between them, therefore I would need a more complex structure to handle that. – Thiago Jun 06 '16 at 17:06
  • I will mark that suggestion as solution because, after all, I have done this in my tests. Thanks! – Thiago Jun 22 '16 at 14:28