Skip to content

Commit

Permalink
Improved type initialization error handling by calling cctor on expre…
Browse files Browse the repository at this point in the history
…ssion tree build and moving logic to Registration. Also moved tests to new class. #812
  • Loading branch information
dotnetjunkie committed Jan 8, 2021
1 parent 3e752b2 commit b9eb362
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 135 deletions.
100 changes: 0 additions & 100 deletions src/SimpleInjector.Tests.Unit/InstanceProducerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,98 +356,6 @@ public void GetInstance_ForSingletonValueType_CanBeResolved()
Assert.AreEqual(expectedValue, actualValue);
}

// #812
[TestMethod]
public void GetInstance_ResolvingATypeWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<TopLevelClassWithFailingTypeInitializer>();

// Act
Action action = () => container.GetInstance<TopLevelClassWithFailingTypeInitializer>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for TopLevelClassWithFailingTypeInitializer threw an " +
"exception. <Inner exception>.",
action);
}

// #812
[TestMethod]
public void GetInstance_ResolvingANestedTypeWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<NestedClassWithFailingTypeInitializer>();

// Act
Action action = () => container.GetInstance<NestedClassWithFailingTypeInitializer>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for InstanceProducerTests.NestedClassWithFailingTypeInitializer " +
"threw an exception. <Inner exception>.",
action);
}

// #812
[TestMethod]
public void GetInstance_ResolvingAGenericNestedTypeWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register(typeof(NestedClassWithFailingTypeInitializer<>));

// Act
Action action = () => container.GetInstance<NestedClassWithFailingTypeInitializer<object>>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for InstanceProducerTests" +
".NestedClassWithFailingTypeInitializer<object> threw an exception. <Inner exception>.",
action);
}

// #812
[TestMethod]
public void GetInstance_ResolvingASingletonWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.RegisterSingleton<NestedClassWithFailingTypeInitializer>();

// Act
Action action = () => container.GetInstance<NestedClassWithFailingTypeInitializer>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for InstanceProducerTests.NestedClassWithFailingTypeInitializer " +
"threw an exception. <Inner exception>.",
action);
}

public class NestedClassWithFailingTypeInitializer
{
static NestedClassWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}
}

public class NestedClassWithFailingTypeInitializer<T>
{
static NestedClassWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}
}

public class OneAndTwo : IOne, ITwo
{
}
Expand All @@ -473,12 +381,4 @@ public NodeFactory(IEnumerable<INode> nodes)
}
}
}

public class TopLevelClassWithFailingTypeInitializer
{
static TopLevelClassWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}
}
}
187 changes: 187 additions & 0 deletions src/SimpleInjector.Tests.Unit/TypeInitializationErrorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
namespace SimpleInjector.Tests.Unit
{
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

public class TopLevelClassWithFailingTypeInitializer
{
static TopLevelClassWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}
}

// #812
[TestClass]
public class TypeInitializationErrorTests
{
[TestMethod]
public void GetInstance_ResolvingATypeWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<TopLevelClassWithFailingTypeInitializer>();

// Act
Action action = () => container.GetInstance<TopLevelClassWithFailingTypeInitializer>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for TopLevelClassWithFailingTypeInitializer threw an " +
"exception. <Inner exception>.",
action);
}

[TestMethod]
public void GetInstance_ResolvingANestedTypeWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<NestedClassWithFailingTypeInitializer>();

// Act
Action action = () => container.GetInstance<NestedClassWithFailingTypeInitializer>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for " +
$"{nameof(TypeInitializationErrorTests)}.NestedClassWithFailingTypeInitializer " +
"threw an exception. <Inner exception>.",
action);
}

[TestMethod]
public void GetInstance_ResolvingAGenericNestedTypeWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register(typeof(NestedClassWithFailingTypeInitializer<>));

// Act
Action action = () => container.GetInstance<NestedClassWithFailingTypeInitializer<object>>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for " +
$"{nameof(TypeInitializationErrorTests)}.NestedClassWithFailingTypeInitializer<object> " +
"threw an exception. <Inner exception>.",
action);
}

[TestMethod]
public void GetInstance_ResolvingASingletonWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.RegisterSingleton<NestedClassWithFailingTypeInitializer>();

// Act
Action action = () => container.GetInstance<NestedClassWithFailingTypeInitializer>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for " +
$"{nameof(TypeInitializationErrorTests)}.NestedClassWithFailingTypeInitializer " +
"threw an exception. <Inner exception>.",
action);
}

[TestMethod]
public void GetInstance_ResolvingADecoratedInstanceWithInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<INonGenericService, NestedClassWithFailingTypeInitializer>();
container.RegisterDecorator<INonGenericService, NonGenericServiceDecorator>();

// Act
Action action = () => container.GetInstance<INonGenericService>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for " +
$"{nameof(TypeInitializationErrorTests)}.NestedClassWithFailingTypeInitializer " +
"threw an exception. <Inner exception>.",
action);
}

[TestMethod]
public void GetInstance_ResolvingADecoratorWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<ICommandHandler<RealCommand>, RealCommandHandler>();
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(DecoratorWithFailingTypeInitializer<>));

// Act
Action action = () => container.GetInstance<ICommandHandler<RealCommand>>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for " +
$"{nameof(TypeInitializationErrorTests)}.DecoratorWithFailingTypeInitializer<RealCommand> " +
"threw an exception. <Inner exception>.",
action);
}

[TestMethod]
public void GetInstance_ResolvingANestedDecoratorWithTypeInitializationException_ThrowsExpressiveException()
{
// Act
var container = new Container();

container.Register<ICommandHandler<RealCommand>, RealCommandHandler>();
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(CommandHandlerDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(DecoratorWithFailingTypeInitializer<>));
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(CommandHandlerDecorator<>));

// Act
Action action = () => container.GetInstance<ICommandHandler<RealCommand>>();

// Assert
AssertThat.ThrowsWithExceptionMessageContains<ActivationException>(
"The type initializer for " +
$"{nameof(TypeInitializationErrorTests)}.DecoratorWithFailingTypeInitializer<RealCommand> " +
"threw an exception. <Inner exception>.",
action);
}

public class NestedClassWithFailingTypeInitializer : INonGenericService
{
static NestedClassWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}

public void DoSomething()
{
}
}

public class NestedClassWithFailingTypeInitializer<T>
{
static NestedClassWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}
}

public class DecoratorWithFailingTypeInitializer<T> : ICommandHandler<T>
{
static DecoratorWithFailingTypeInitializer()
{
throw new Exception("<Inner exception>.");
}

public DecoratorWithFailingTypeInitializer(ICommandHandler<T> decoratee)
{
}
}
}
}
36 changes: 3 additions & 33 deletions src/SimpleInjector/InstanceProducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,6 @@ public Expression BuildExpression()

throw;
}
catch (TypeInitializationException ex)
{
throw this.MakeMoreExpressiveTypeInitializationException(ex);
}
catch (Exception ex)
{
if (this.MustWrapThrownException(ex))
Expand Down Expand Up @@ -615,16 +611,9 @@ private object BuildAndReplaceInstanceCreatorAndCreateFirstInstance()
{
this.instanceCreator = this.BuildInstanceCreator();

try
{
var instance = this.instanceCreator();
this.InstanceSuccessfullyCreated = true;
return instance;
}
catch (TypeInitializationException ex)
{
throw this.MakeMoreExpressiveTypeInitializationException(ex);
}
var instance = this.instanceCreator();
this.InstanceSuccessfullyCreated = true;
return instance;
}

// Prevents any recursive calls from taking place.
Expand Down Expand Up @@ -686,25 +675,6 @@ private static bool ShouldBeRegisteredAsAnExternalProducer(Registration registra
return !(registration is ExpressionRegistration);
}

private Exception MakeMoreExpressiveTypeInitializationException(
TypeInitializationException ex)
{
Type implementationType = this.Registration.ImplementationType;

// #812 The exceptions thrown by the runtime in case of a type initialization error are
// unproductive. Here we throw a more expressive exception. This is done by appending the
// inner exception's message to the exception message and replacing the type name with a
// 'friendly type name', which is especially useful in the case of generic types.
return new ActivationException(
ex.Message
// When the type in question is nested, the exception will contain just the simple
// type name, while for non-nested types, the full name is used (don't ask why).
.Replace($"'{implementationType.FullName}'", implementationType.ToFriendlyName())
.Replace($"'{implementationType.Name}'", implementationType.ToFriendlyName()) +
" " + ex.InnerException?.Message,
ex);
}

[ExcludeFromCodeCoverage]
internal sealed class InstanceProducerDebugView
{
Expand Down
Loading

0 comments on commit b9eb362

Please sign in to comment.