chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
0 votes
When I use WillThrow to throw an exception from a fake that's called from a faked event, I end up with a TargetInvocationException rather than the exception I asked to throw.

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TypeMock.ArrangeActAssert;

namespace TestProject1
{
    [TestClass]
    public class UnitTest1
    {
        class EventServer
        {
            public event EventHandler Event;
        }

        class OtherClass
        {
            public void DoSomething(string message, params Object[] parameters)
            {
            }
        }

        class EventListener
        {
            private OtherClass _otherClass;
            public EventListener(EventServer es, OtherClass otherClass)
            {
                es.Event += new EventHandler(es_Event);
                _otherClass = otherClass;
            }

            void es_Event(object sender, EventArgs e)
            {
                _otherClass.DoSomething(String.Empty);
            }
        }

        [Isolated]
        [TestMethod]
        public void TestMethod1()
        {
            EventServer es = Isolate.Fake.Instance<EventServer>();
            OtherClass oc = Isolate.Fake.Instance<OtherClass>();
            EventListener target = new EventListener(es, oc);
            bool exceptionThrown = false;

            Isolate.WhenCalled(() => oc.DoSomething(String.Empty)).WillThrow(new ArgumentException());

            try
            {
                Isolate.Invoke.Event(() => es.Event += (s, a) => { }, null, new EventArgs());
            }
            catch (ArgumentException)
            {
                exceptionThrown = true;
            }

            Assert.IsTrue(exceptionThrown);
        }
    }
}


I'm typically using 6.0.8, but I installed the latest version, 7.3, and I see the same behavior. My expectation is that the above test should pass. Is there something I'm missing?

Thanks,
-Kevin
asked by kevinms99 (4.4k points)

2 Answers

0 votes
Hi Kevin,

We've confirmed it's a bug. Until we fix this, here are two possible work around a workaround:
1. Instead of catching ArgumentException, catch the TargetInvocationException then the inner exception is going to be the exception you're looking for. For example, the catch clause can look like that:

catch (System.Reflection.TargetInvocationException e)
{
    if (e.InnerException.GetType() == typeof(ArgumentException))
    {
        exceptionThrown = true;
    }
}


2. Instead of invoking the event, you can invoke the event handler method directly. So instead of:
Isolate.Invoke.Event(() => es.Event += (s, a) => { }, null, new EventArgs());

You can use:
Isolate.Invoke.Method(target, "es_Event", null, new EventArgs());


I'll let you know when we fix this.
answered by gilz (14.5k points)
0 votes
Thanks, Gil.

I tried the first workaround, but it didn't work for my case. The code I was testing handles some exceptions but not others -- this was the functionality I was testing. Since the code under test never expects a TargetInvocationException, it would always bubble up the exception, so the exception handling code never executed.

I have another workaround in place to redirect the event sign-up to an event in a class managed by my unit test code.

        private class ExceptionThrownEventFake
        {
            /// <summary>
            /// Fire the ExceptionThrown event to any client plugged in
            /// </summary>
            /// <param name="exceptionThrown">The argument passed in the event</param>
            public void FireExceptionThrown(Exception exceptionThrown)
            {
                if (ExceptionThrown != null)
                {
                    ExceptionThrown(exceptionThrown);
                }
            }

            /// <summary>
            /// The fake ExceptionThrown event
            /// </summary>
            public event ExceptionThrownHandler ExceptionThrown;
        }


I redirect the event registration to this event faker:

            ExceptionThrownEventFake eventFake = new ExceptionThrownEventFake();
            Isolate.WhenCalled(() => originalEventProvider.ExceptionThrown += null).DoInstead((context) =>
            {
                ExceptionThrownHandler handler = (ExceptionThrownHandler)context.Parameters[0];
                eventFake.ExceptionThrown += handler;
            });
            ...
                    eventFake.FireExceptionThrown(exceptionToThrow);


This is obviously more code than your example, but it doesn't require me to pass functions by string names, which I find pretty unmaintainable. :D

Thanks,
-Kevin
answered by kevinms99 (4.4k points)
...