Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UserCallbackException is thrown when another Exception is expected #1543

Closed
waldfee opened this issue Dec 13, 2018 · 3 comments
Closed

UserCallbackException is thrown when another Exception is expected #1543

waldfee opened this issue Dec 13, 2018 · 3 comments
Milestone

Comments

@waldfee
Copy link

waldfee commented Dec 13, 2018

I upgraded FakeItEasy from 2.3.1 to 4.9.1 and I have the following issue.

Scenario:

  1. A Fake is configured to throw
  2. Another Fake is configured to indirectly call the throwing fake
  3. The exception from the first Fake is catched
  4. Another exception is thrown in the catch block

Expected behaviour:
The exception thrown in the catch block is raised to the caller

Observed behaviour:
An UserCallbackException is raised to the caller

Workarounds:

  • Unpack the UserCallbackException and assert it's InnerException
  • Use a manually written Fake for the throwing interface

This behaviour seems to have been introduced with #1364
Is this behaviour intended, and is there a way to configure it, or do I have to use one of the workarounds?

FakeItEasy version: 4.9.1, also happens in 5.0.0-alpha.1
.NET Framework version: 4.7.2

Minimal repro:

[TestClass]
public class UserCallbackExceptionTest
{
	public class CatchedException : Exception
	{
	}

	public class ThrowedException : Exception
	{
	}

	public interface IThrows
	{
		void ThrowIt();
	}

	public interface IDoStuff
	{
		void Execute(Action operation);
	}

	public class UnderTest
	{
		private readonly IThrows _throws;
		private readonly IDoStuff _dostuff;

		public UnderTest(IThrows throws, IDoStuff dostuff)
		{
			_throws = throws;
			_dostuff = dostuff;
		}

		public void DoStuff()
		{
			_dostuff.Execute(DoStuffPrivately);
		}

		private void DoStuffPrivately()
		{
			try
			{
				_throws.ThrowIt();
			}
			catch (CatchedException)
			{
				throw new ThrowedException();
			}
		}
	}

	public class HandWrittenFakeDoStuff : IDoStuff
	{
		public void Execute(Action operation)
		{
			operation();
		}
	}

	[TestMethod]
	public void WorksWhenUsingHandWrittenFake()
	{
		IThrows throws = A.Fake<IThrows>();
		A.CallTo(() => throws.ThrowIt())
			.Throws(new CatchedException());

		UnderTest underTest = new UnderTest(throws, new HandWrittenFakeDoStuff());

		Assert.ThrowsException<ThrowedException>(() => underTest.DoStuff());
	}

	[TestMethod]
	public void DoesNotWorkWhenUsingFakeItEasyFake()
	{
		IDoStuff doStuff = A.Fake<IDoStuff>();
		A.CallTo(() => doStuff.Execute(A<Action>.Ignored))
			.Invokes(call =>
					 {
						 // assume the real implementation does other things beside calling the action
						 // to avoid those sideeffects we fake it
						 Action operation = call.GetArgument<Action>(0);
						 operation();
					 });

		IThrows throws = A.Fake<IThrows>();
		A.CallTo(() => throws.ThrowIt())
			.Throws(new CatchedException());

		UnderTest underTest = new UnderTest(throws, doStuff);

		Assert.ThrowsException<ThrowedException>(() => underTest.DoStuff());
	}
}
@thomaslevesque
Copy link
Member

Hi @waldfee,

Thanks for reporting this. At first glance, it looks like it's working as designed, but I see how this can be a problem in your case. Basically, starting with #1364, we wrap exceptions that occur in user callbacks, but in retrospect it might not be the right thing to do for Invokes. The callback specified in Invokes actually stands for the body of the faked method, so it could legitimately do anything the method body could do, including throwing an exception.

We'll fix this ASAP.

@blairconrad
Copy link
Member

Oh, and @waldfee, great explanation, reproduction, and assessment.

@thomaslevesque
Copy link
Member

This fix has been released as part of FakeItEasy 4.9.2.

Thanks, @waldfee! The reproduction instructions were excellent. Look for your name in the release notes. 🏆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants