From 54c57ac56dd4eeaba7f4aa50bcfd1bb202d820c5 Mon Sep 17 00:00:00 2001 From: Casey White Date: Wed, 30 Aug 2023 12:30:57 -0700 Subject: [PATCH 1/2] Add InstanceResolver --- .gitignore | 1 + Moq.AutoMock.Tests/DescribeCreateInstance.cs | 28 +++++++++++++++++--- Moq.AutoMock.Tests/DescribeGetMock.cs | 8 ++++++ Moq.AutoMock.Tests/Util/SealedService.cs | 9 +++++++ Moq.AutoMock.Tests/Util/WithSealedService.cs | 14 ++++++++++ Moq.AutoMock/AutoMocker.cs | 13 ++++++++- Moq.AutoMock/Resolvers/InstanceResolver.cs | 19 +++++++++++++ 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 Moq.AutoMock.Tests/Util/SealedService.cs create mode 100644 Moq.AutoMock.Tests/Util/WithSealedService.cs create mode 100644 Moq.AutoMock/Resolvers/InstanceResolver.cs diff --git a/.gitignore b/.gitignore index 4f266ede..00e12783 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.vs +*.idea/ bin/ obj/ diff --git a/Moq.AutoMock.Tests/DescribeCreateInstance.cs b/Moq.AutoMock.Tests/DescribeCreateInstance.cs index 17ecc5db..55a8b86c 100644 --- a/Moq.AutoMock.Tests/DescribeCreateInstance.cs +++ b/Moq.AutoMock.Tests/DescribeCreateInstance.cs @@ -1,7 +1,5 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq.AutoMock.Resolvers; -using Moq.AutoMock.Tests.Util; namespace Moq.AutoMock.Tests; @@ -208,7 +206,7 @@ public void It_includes_reason_why_constructor_was_rejected() public void It_includes_reason_why_nested_constructor_was_rejected() { AutoMocker mocker = new(); - //Need to remove this resolver to prevent AM from attempting to simply mock the values (which will throw a Moq exception) + //Need to remove these resolvers to prevent AM from attempting to simply mock the values (which will throw a Moq exception), or create an instance mocker.Resolvers.Remove(mocker.Resolvers.OfType().Single()); ObjectCreationException ex = Assert.ThrowsException(() => mocker.CreateInstance()); @@ -218,6 +216,16 @@ public void It_includes_reason_why_nested_constructor_was_rejected() Assert.AreEqual("Rejecting constructor Moq.AutoMock.Tests.DescribeCreateInstance+HasMultipleConstructors(Moq.AutoMock.Tests.DescribeCreateInstance+HasStringParameter hasString), because AutoMocker was unable to create parameter 'Moq.AutoMock.Tests.DescribeCreateInstance+HasStringParameter hasString'", ex.DiagnosticMessages[1]); } + [TestMethod] + public void It_can_create_instances_of_nested_sealed_classes() + { + AutoMocker mocker = new(); + mocker.Resolvers.Add(new InstanceResolver()); + var mockWithSealedService = mocker.CreateInstance(); + + Assert.AreEqual(mockWithSealedService.SealedService, mockWithSealedService.NestedSealedService.SealedService); + } + private class CustomStringResolver : IMockResolver { public CustomStringResolver(string stringValue) @@ -267,6 +275,18 @@ public HasMultipleConstructorsNested(HasStringParameter hasString) } } + public class HasNestedSealedService + { + public SealedService SealedService { get; set; } + public WithSealedService NestedSealedService { get; set; } + + public HasNestedSealedService(SealedService sealedService, WithSealedService nestedSealedService) + { + SealedService = sealedService; + NestedSealedService = nestedSealedService; + } + } + public record class ConcreteDependency(IService1 Service); public record class ConcreteDependencyIsFirst(ConcreteDependency Dependency, IService1 Service); public record class ConcreteDependencyIsSecond(IService1 Service, ConcreteDependency Dependency); diff --git a/Moq.AutoMock.Tests/DescribeGetMock.cs b/Moq.AutoMock.Tests/DescribeGetMock.cs index 3dcb5550..0c986724 100644 --- a/Moq.AutoMock.Tests/DescribeGetMock.cs +++ b/Moq.AutoMock.Tests/DescribeGetMock.cs @@ -82,4 +82,12 @@ public void It_returns_null_for_unmockable_object_via_iserviceprovider() var service = mocker.GetService(typeof(string)); Assert.IsNull(service); } + + [TestMethod] + public void It_throws_when_mocking_a_sealed_class() + { + var mocker = new AutoMocker(); + var act = () => mocker.GetMock(); + Assert.ThrowsException(act); + } } diff --git a/Moq.AutoMock.Tests/Util/SealedService.cs b/Moq.AutoMock.Tests/Util/SealedService.cs new file mode 100644 index 00000000..64423adb --- /dev/null +++ b/Moq.AutoMock.Tests/Util/SealedService.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Moq.AutoMock.Tests.Util; + +[ExcludeFromCodeCoverage] +public sealed class SealedService +{ + public SealedService() { } +} diff --git a/Moq.AutoMock.Tests/Util/WithSealedService.cs b/Moq.AutoMock.Tests/Util/WithSealedService.cs new file mode 100644 index 00000000..2cbc286a --- /dev/null +++ b/Moq.AutoMock.Tests/Util/WithSealedService.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Moq.AutoMock.Tests.Util; + +[ExcludeFromCodeCoverage] +public class WithSealedService +{ + public SealedService SealedService { get; set; } + + public WithSealedService(SealedService service) + { + SealedService = service; + } +} diff --git a/Moq.AutoMock/AutoMocker.cs b/Moq.AutoMock/AutoMocker.cs index f44e8a4f..8a547218 100644 --- a/Moq.AutoMock/AutoMocker.cs +++ b/Moq.AutoMock/AutoMocker.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Runtime.ExceptionServices; using System.Text; +using Moq.AutoMock.Extensions; using Moq.AutoMock.Resolvers; using Moq.Language; using Moq.Language.Flow; @@ -78,7 +79,7 @@ public AutoMocker(MockBehavior mockBehavior, DefaultValue defaultValue, DefaultV new LazyResolver(), new FuncResolver(), new CancellationTokenResolver(), - new MockResolver(mockBehavior, defaultValue, defaultValueProvider, callBase), + new MockResolver(mockBehavior, defaultValue, defaultValueProvider, callBase) }; } @@ -219,6 +220,12 @@ public object CreateInstance(Type type, bool enablePrivate) if (type is null) throw new ArgumentNullException(nameof(type)); var context = new ObjectGraphContext(enablePrivate); + + return CreateInstanceInternal(type, context); + } + + internal object CreateInstanceInternal(Type type, ObjectGraphContext context) + { if (!TryGetConstructorInvocation(type, context, out ConstructorInfo? ctor, out IInstance[]? arguments)) { throw new ObjectCreationException( @@ -987,6 +994,10 @@ public void Verify(Expression> expression, Times ti internal Mock? CreateMock(Type serviceType, MockBehavior mockBehavior, DefaultValue defaultValue, DefaultValueProvider? defaultValueProvider, bool callBase, ObjectGraphContext objectGraphContext) { + if (!serviceType.IsMockable()) + { + return null; + } var mockType = typeof(Mock<>).MakeGenericType(serviceType); bool mayHaveDependencies = serviceType.IsClass diff --git a/Moq.AutoMock/Resolvers/InstanceResolver.cs b/Moq.AutoMock/Resolvers/InstanceResolver.cs new file mode 100644 index 00000000..346dabe3 --- /dev/null +++ b/Moq.AutoMock/Resolvers/InstanceResolver.cs @@ -0,0 +1,19 @@ +namespace Moq.AutoMock.Resolvers; + +/// +/// A resolver that resolves requested types with a created instance. +/// +public class InstanceResolver : IMockResolver +{ + /// + /// Resolves requested types with created instances. + /// + /// The resolution context. + public void Resolve(MockResolutionContext context) + { + if (context.AutoMocker.CreateInstanceInternal(context.RequestType, context.ObjectGraphContext) is { } instance) + { + context.Value = instance; + } + } +} From 210dc1fa28cb7df2c28d7736718c407c9cd2b88f Mon Sep 17 00:00:00 2001 From: Casey White Date: Wed, 30 Aug 2023 12:44:01 -0700 Subject: [PATCH 2/2] revert comment --- Moq.AutoMock.Tests/DescribeCreateInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moq.AutoMock.Tests/DescribeCreateInstance.cs b/Moq.AutoMock.Tests/DescribeCreateInstance.cs index 55a8b86c..28d168f9 100644 --- a/Moq.AutoMock.Tests/DescribeCreateInstance.cs +++ b/Moq.AutoMock.Tests/DescribeCreateInstance.cs @@ -206,7 +206,7 @@ public void It_includes_reason_why_constructor_was_rejected() public void It_includes_reason_why_nested_constructor_was_rejected() { AutoMocker mocker = new(); - //Need to remove these resolvers to prevent AM from attempting to simply mock the values (which will throw a Moq exception), or create an instance + //Need to remove this resolver to prevent AM from attempting to simply mock the values (which will throw a Moq exception) mocker.Resolvers.Remove(mocker.Resolvers.OfType().Single()); ObjectCreationException ex = Assert.ThrowsException(() => mocker.CreateInstance());