Skip to content

Commit

Permalink
Adding functionality to prevent caching (#299)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Hewitt <[email protected]>
  • Loading branch information
Keboo and adamhewitt627 committed May 24, 2024
1 parent 0ad0d26 commit fa9c82b
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 141 deletions.
112 changes: 0 additions & 112 deletions Moq.AutoMock.Tests/ConstructorTests.cs

This file was deleted.

25 changes: 19 additions & 6 deletions Moq.AutoMock.Tests/DescribeResolvedObjects.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics.CodeAnalysis;
using Moq.AutoMock.Resolvers;
using Moq.AutoMock.Tests.Util;

namespace Moq.AutoMock.Tests;

Expand Down Expand Up @@ -78,7 +74,7 @@ public void ResolvedObjects_custom_resolver_providing_value_prevents_subsequent_
}

[TestMethod]
public void ResolvedObjects_custom_resolver_can_prempt_cache_resolver()
public void ResolvedObjects_custom_resolver_can_preempt_cache_resolver()
{
object singleton = new();
object used = new();
Expand All @@ -90,6 +86,20 @@ public void ResolvedObjects_custom_resolver_can_prempt_cache_resolver()
Assert.AreEqual(singleton, resolved);
}

[TestMethod]
public void ResolvedObject_does_not_contain_custom_resolved_type_when_excluded()
{
object singleton = new();
AutoMocker mocker = new();
var cacheResolver = mocker.Resolvers.OfType<CacheResolver>().Single();
var index = mocker.Resolvers.IndexOf(cacheResolver);
mocker.Resolvers.Insert(index + 1, new SingletonResolver<object>(singleton) { NoCache = true });

object resolved = mocker.Get<object>();
Assert.AreEqual(singleton, resolved);
Assert.IsFalse(mocker.ResolvedObjects.Values.Contains(singleton));
}

[ExcludeFromCodeCoverage]
private class ThrowingResolver : IMockResolver
{
Expand All @@ -106,11 +116,14 @@ public SingletonResolver(T value)

public T Value { get; }

public bool NoCache { get; set; }

public void Resolve(MockResolutionContext context)
{
if (context.RequestType == typeof(T))
{
context.Value = Value;
context.NoCache = NoCache;
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Moq.AutoMock.Tests/Moq.AutoMock.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,10 @@
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<ProjectReference Include="..\Moq.AutoMock\Moq.AutoMock.csproj" />

<ProjectReference Include="..\Moq.AutoMocker.Generators\Moq.AutoMocker.Generators.csproj">
<OutputItemType>Analyzer</OutputItemType>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
</Project>
16 changes: 7 additions & 9 deletions Moq.AutoMock.Tests/Resolvers/MockResolutionContextTests.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq.AutoMock.Resolvers;
using Moq.AutoMock.Resolvers;

namespace Moq.AutoMock.Tests.Resolvers;

[TestClass]
public class MockResolutionContextTests
[ConstructorTests(typeof(MockResolutionContext))]
public partial class MockResolutionContextTests
{
[TestMethod]
public void Constructor_null_checks_arguments()
=> ConstructorTest
.BuildArgumentNullExceptionsTest<MockResolutionContext>()
.Use(new ObjectGraphContext(false))
.Run();
partial void AutoMockerTestSetup(AutoMocker mocker, string testName)
{
mocker.Use(new ObjectGraphContext(false));
}
}
34 changes: 21 additions & 13 deletions Moq.AutoMock/AutoMocker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,14 @@ private NonBlocking.ConcurrentDictionary<Type, IInstance>? TypeMap

private bool TryResolve(Type serviceType,
ObjectGraphContext resolutionContext,
[NotNullWhen(true)] out IInstance? instance)
[NotNullWhen(true)] out IInstance? instance,
out bool noCache)
{
if (resolutionContext.VisitedTypes.Contains(serviceType))
{
//Rejected due to circular dependency
instance = null;
noCache = false;
return false;
}

Expand All @@ -156,6 +158,7 @@ private bool TryResolve(Type serviceType,
if (!context.ValueProvided)
{
instance = null;
noCache = false;
return false;
}

Expand All @@ -165,6 +168,7 @@ private bool TryResolve(Type serviceType,
IInstance i => i,
_ => new RealInstance(context.Value),
};
noCache = context.NoCache;
return true;
}

Expand Down Expand Up @@ -633,9 +637,9 @@ public object Get(Type serviceType, bool enablePrivate)

private object Get(Type serviceType, ObjectGraphContext context)
{
if (TryGet(serviceType, context, out IInstance? service))
if (TryGet(serviceType, context, out IInstance? service, out bool noCache))
{
if (TypeMap is { } typeMap && !typeMap.ContainsKey(serviceType))
if (!noCache && TypeMap is { } typeMap && !typeMap.ContainsKey(serviceType))
{
typeMap[serviceType] = service;
}
Expand All @@ -647,11 +651,12 @@ private object Get(Type serviceType, ObjectGraphContext context)
internal bool TryGet(
Type serviceType,
ObjectGraphContext context,
[NotNullWhen(true)] out IInstance? service)
[NotNullWhen(true)] out IInstance? service,
out bool noCache)
{
if (serviceType is null) throw new ArgumentNullException(nameof(serviceType));

if (TryResolve(serviceType, context, out IInstance? instance))
if (TryResolve(serviceType, context, out IInstance? instance, out noCache))
{
service = instance;
return true;
Expand All @@ -663,9 +668,9 @@ internal bool TryGet(
/// <inheritdoc />
object? IServiceProvider.GetService(Type serviceType)
{
if (TryGet(serviceType, new ObjectGraphContext(false), out IInstance? service))
if (TryGet(serviceType, new ObjectGraphContext(false), out IInstance? service, out bool noCache))
{
if (TypeMap is { } typeMap && !typeMap.ContainsKey(serviceType))
if (!noCache && TypeMap is { } typeMap && !typeMap.ContainsKey(serviceType))
{
typeMap[serviceType] = service;
}
Expand Down Expand Up @@ -724,10 +729,10 @@ public Mock GetMock(Type serviceType, bool enablePrivate)

private Mock GetMockImplementation(Type serviceType, bool enablePrivate)
{
if (TryResolve(serviceType, new ObjectGraphContext(enablePrivate), out IInstance? instance) &&
if (TryResolve(serviceType, new ObjectGraphContext(enablePrivate), out IInstance? instance, out bool noCache) &&
instance.IsMock)
{
if (TypeMap is { } typeMap && !typeMap.ContainsKey(serviceType))
if (!noCache && TypeMap is { } typeMap && !typeMap.ContainsKey(serviceType))
{
typeMap[serviceType] = instance;
}
Expand Down Expand Up @@ -1077,13 +1082,16 @@ bool TryCreateArguments(ConstructorInfo constructor, ObjectGraphContext context,
for (int i = 0; i < parameters.Length; i++)
{
ObjectGraphContext parameterContext = new(context);
if (!TryGet(parameters[i].ParameterType, parameterContext, out IInstance? service))
if (!TryGet(parameters[i].ParameterType, parameterContext, out IInstance? service, out bool noCache))
{
context.AddDiagnosticMessage($"Rejecting constructor {GetConstructorDisplayString(constructor)}, because {nameof(AutoMocker)} was unable to create parameter '{parameters[i].ParameterType.FullName} {parameters[i].Name}'");
return false;
}

EnsureCached(parameters[i].ParameterType, service);
if (!noCache)
{
EnsureCached(parameters[i].ParameterType, service);
}
arguments[i] = service;
}
return true;
Expand Down Expand Up @@ -1124,8 +1132,8 @@ private void EnsureCached(Type type, IInstance instance)

private Mock GetOrMakeMockFor(Type type)
{
if (TryResolve(type, new ObjectGraphContext(false), out IInstance? instance) &&
instance is MockInstance mockInstance)
if (TryResolve(type, new ObjectGraphContext(false), out IInstance? instance, out bool noCache) &&
instance is MockInstance mockInstance && !noCache)
{
EnsureCached(type, mockInstance);
return mockInstance.Mock;
Expand Down
3 changes: 2 additions & 1 deletion Moq.AutoMock/Resolvers/ArrayResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ public void Resolve(MockResolutionContext context)
{
Type elmType = context.RequestType.GetElementType() ?? throw new InvalidOperationException($"Could not determine element type for '{context.RequestType}'");
MockArrayInstance arrayInstance = new(elmType);
if (context.AutoMocker.TryGet(elmType, context.ObjectGraphContext, out IInstance? instance))
if (context.AutoMocker.TryGet(elmType, context.ObjectGraphContext, out IInstance? instance, out bool noCache))
{
arrayInstance.Add(instance);
context.NoCache = noCache;
}
context.Value = arrayInstance;
}
Expand Down
5 changes: 5 additions & 0 deletions Moq.AutoMock/Resolvers/MockResolutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public object? Value
/// </summary>
internal bool ValueProvided { get; private set; }

/// <summary>
/// When set to true, the resolution will not use the cache to resolve resolved instance set in <see cref="Value"/>
/// </summary>
public bool NoCache { get; set; }

/// <summary>
/// Deconstruct this instance into its individual properties.
/// </summary>
Expand Down

0 comments on commit fa9c82b

Please sign in to comment.