Skip to content

Commit 5fe9df5

Browse files
Cwhite534Casey White
and
Casey White
authored
Prevent sealed class mocks (#237)
* Add InstanceResolver * revert comment --------- Co-authored-by: Casey White <[email protected]>
1 parent f2313d7 commit 5fe9df5

File tree

7 files changed

+86
-4
lines changed

7 files changed

+86
-4
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
/.vs
3+
*.idea/
34

45
bin/
56
obj/

Diff for: Moq.AutoMock.Tests/DescribeCreateInstance.cs

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
using System;
2-
using Microsoft.VisualStudio.TestTools.UnitTesting;
1+

32
using Moq.AutoMock.Resolvers;
4-
using Moq.AutoMock.Tests.Util;
53

64
namespace Moq.AutoMock.Tests;
75

@@ -218,6 +216,16 @@ public void It_includes_reason_why_nested_constructor_was_rejected()
218216
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]);
219217
}
220218

219+
[TestMethod]
220+
public void It_can_create_instances_of_nested_sealed_classes()
221+
{
222+
AutoMocker mocker = new();
223+
mocker.Resolvers.Add(new InstanceResolver());
224+
var mockWithSealedService = mocker.CreateInstance<HasNestedSealedService>();
225+
226+
Assert.AreEqual(mockWithSealedService.SealedService, mockWithSealedService.NestedSealedService.SealedService);
227+
}
228+
221229
private class CustomStringResolver : IMockResolver
222230
{
223231
public CustomStringResolver(string stringValue)
@@ -267,6 +275,18 @@ public HasMultipleConstructorsNested(HasStringParameter hasString)
267275
}
268276
}
269277

278+
public class HasNestedSealedService
279+
{
280+
public SealedService SealedService { get; set; }
281+
public WithSealedService NestedSealedService { get; set; }
282+
283+
public HasNestedSealedService(SealedService sealedService, WithSealedService nestedSealedService)
284+
{
285+
SealedService = sealedService;
286+
NestedSealedService = nestedSealedService;
287+
}
288+
}
289+
270290
public record class ConcreteDependency(IService1 Service);
271291
public record class ConcreteDependencyIsFirst(ConcreteDependency Dependency, IService1 Service);
272292
public record class ConcreteDependencyIsSecond(IService1 Service, ConcreteDependency Dependency);

Diff for: Moq.AutoMock.Tests/DescribeGetMock.cs

+8
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,12 @@ public void It_returns_null_for_unmockable_object_via_iserviceprovider()
8282
var service = mocker.GetService(typeof(string));
8383
Assert.IsNull(service);
8484
}
85+
86+
[TestMethod]
87+
public void It_throws_when_mocking_a_sealed_class()
88+
{
89+
var mocker = new AutoMocker();
90+
var act = () => mocker.GetMock<SealedService>();
91+
Assert.ThrowsException<ArgumentException>(act);
92+
}
8593
}

Diff for: Moq.AutoMock.Tests/Util/SealedService.cs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Moq.AutoMock.Tests.Util;
4+
5+
[ExcludeFromCodeCoverage]
6+
public sealed class SealedService
7+
{
8+
public SealedService() { }
9+
}

Diff for: Moq.AutoMock.Tests/Util/WithSealedService.cs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Moq.AutoMock.Tests.Util;
4+
5+
[ExcludeFromCodeCoverage]
6+
public class WithSealedService
7+
{
8+
public SealedService SealedService { get; set; }
9+
10+
public WithSealedService(SealedService service)
11+
{
12+
SealedService = service;
13+
}
14+
}

Diff for: Moq.AutoMock/AutoMocker.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Reflection;
44
using System.Runtime.ExceptionServices;
55
using System.Text;
6+
using Moq.AutoMock.Extensions;
67
using Moq.AutoMock.Resolvers;
78
using Moq.Language;
89
using Moq.Language.Flow;
@@ -78,7 +79,7 @@ public AutoMocker(MockBehavior mockBehavior, DefaultValue defaultValue, DefaultV
7879
new LazyResolver(),
7980
new FuncResolver(),
8081
new CancellationTokenResolver(),
81-
new MockResolver(mockBehavior, defaultValue, defaultValueProvider, callBase),
82+
new MockResolver(mockBehavior, defaultValue, defaultValueProvider, callBase)
8283
};
8384
}
8485

@@ -219,6 +220,12 @@ public object CreateInstance(Type type, bool enablePrivate)
219220
if (type is null) throw new ArgumentNullException(nameof(type));
220221

221222
var context = new ObjectGraphContext(enablePrivate);
223+
224+
return CreateInstanceInternal(type, context);
225+
}
226+
227+
internal object CreateInstanceInternal(Type type, ObjectGraphContext context)
228+
{
222229
if (!TryGetConstructorInvocation(type, context, out ConstructorInfo? ctor, out IInstance[]? arguments))
223230
{
224231
throw new ObjectCreationException(
@@ -987,6 +994,10 @@ public void Verify<T, TResult>(Expression<Func<T, TResult>> expression, Times ti
987994

988995
internal Mock? CreateMock(Type serviceType, MockBehavior mockBehavior, DefaultValue defaultValue, DefaultValueProvider? defaultValueProvider, bool callBase, ObjectGraphContext objectGraphContext)
989996
{
997+
if (!serviceType.IsMockable())
998+
{
999+
return null;
1000+
}
9901001
var mockType = typeof(Mock<>).MakeGenericType(serviceType);
9911002

9921003
bool mayHaveDependencies = serviceType.IsClass

Diff for: Moq.AutoMock/Resolvers/InstanceResolver.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Moq.AutoMock.Resolvers;
2+
3+
/// <summary>
4+
/// A resolver that resolves requested types with a created instance.
5+
/// </summary>
6+
public class InstanceResolver : IMockResolver
7+
{
8+
/// <summary>
9+
/// Resolves requested types with created instances.
10+
/// </summary>
11+
/// <param name="context">The resolution context.</param>
12+
public void Resolve(MockResolutionContext context)
13+
{
14+
if (context.AutoMocker.CreateInstanceInternal(context.RequestType, context.ObjectGraphContext) is { } instance)
15+
{
16+
context.Value = instance;
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)