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

Prevent sealed class mocks #237

Merged
merged 3 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

/.vs
*.idea/

bin/
obj/
Expand Down
26 changes: 23 additions & 3 deletions Moq.AutoMock.Tests/DescribeCreateInstance.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Moq.AutoMock.Resolvers;
using Moq.AutoMock.Tests.Util;

namespace Moq.AutoMock.Tests;

Expand Down Expand Up @@ -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<HasNestedSealedService>();

Assert.AreEqual(mockWithSealedService.SealedService, mockWithSealedService.NestedSealedService.SealedService);
}

private class CustomStringResolver : IMockResolver
{
public CustomStringResolver(string stringValue)
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions Moq.AutoMock.Tests/DescribeGetMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SealedService>();
Assert.ThrowsException<ArgumentException>(act);
}
}
9 changes: 9 additions & 0 deletions Moq.AutoMock.Tests/Util/SealedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Diagnostics.CodeAnalysis;

namespace Moq.AutoMock.Tests.Util;

[ExcludeFromCodeCoverage]
public sealed class SealedService
{
public SealedService() { }
}
14 changes: 14 additions & 0 deletions Moq.AutoMock.Tests/Util/WithSealedService.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 12 additions & 1 deletion Moq.AutoMock/AutoMocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
};
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -987,6 +994,10 @@ public void Verify<T, TResult>(Expression<Func<T, TResult>> 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
Expand Down
19 changes: 19 additions & 0 deletions Moq.AutoMock/Resolvers/InstanceResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Moq.AutoMock.Resolvers;

/// <summary>
/// A resolver that resolves requested types with a created instance.
/// </summary>
public class InstanceResolver : IMockResolver
{
/// <summary>
/// Resolves requested types with created instances.
/// </summary>
/// <param name="context">The resolution context.</param>
public void Resolve(MockResolutionContext context)
{
if (context.AutoMocker.CreateInstanceInternal(context.RequestType, context.ObjectGraphContext) is { } instance)
{
context.Value = instance;
}
}
}
Loading