Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
42 changes: 42 additions & 0 deletions Source/Mockolate/DefaultValueFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;

namespace Mockolate;

/// <summary>
/// Defines a mechanism for generating default values.
/// </summary>
public class DefaultValueFactory
{
private readonly Func<Type, object?[], object?>? _generator;
private readonly Func<Type, bool>? _predicate;
Comment thread
vbreuss marked this conversation as resolved.

/// <summary>
/// This constructor is protected to allow inheritance.
/// </summary>
// ReSharper disable once UnusedMember.Global
protected DefaultValueFactory()
{
}

/// <summary>
/// Creates a new default value factory for types that match the given <paramref name="predicate" />.
/// </summary>
public DefaultValueFactory(Func<Type, bool> predicate, Func<Type, object?[], object?> generator)
{
_predicate = predicate;
_generator = generator;
}

/// <summary>
/// Checks if the factory can generate a value for the specified <paramref name="type" />.
/// </summary>
public virtual bool CanGenerateValue(Type type)
=> _predicate?.Invoke(type) ?? false;

/// <summary>
/// Generates a default value of the specified <paramref name="type" />, with
/// the <paramref name="parameters" /> for context.
/// </summary>
public virtual object? GenerateValue(Type type, params object?[] parameters)
=> _generator?.Invoke(type, parameters);
}
29 changes: 29 additions & 0 deletions Source/Mockolate/MockBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,18 @@ public MockBehavior UseConstructorParametersFor<T>(params object?[] parameters)
return behavior;
}

/// <summary>
/// Uses the given <paramref name="defaultValueFactories" /> to create default values for supported types.
/// </summary>
public MockBehavior WithDefaultValueFor(params DefaultValueFactory[] defaultValueFactories)
{
MockBehavior behavior = this with
{
DefaultValue = new DefaultValueGeneratorWithFactories(DefaultValue, defaultValueFactories),
};
return behavior;
}

private interface IInitializer;

private interface IInitializer<in T> : IInitializer
Expand Down Expand Up @@ -169,4 +181,21 @@ public Action<IMockSetup<T>>[] GetSetups()
return setups.Select(a => new Action<IMockSetup<T>>(s => a(index, s))).ToArray();
}
}

private sealed class DefaultValueGeneratorWithFactories(
IDefaultValueGenerator inner,
DefaultValueFactory[] factories)
: IDefaultValueGenerator
{
public object? GenerateValue(Type type, params object?[] parameters)
{
DefaultValueFactory? factory = factories.FirstOrDefault(factory => factory.CanGenerateValue(type));
Comment thread
vbreuss marked this conversation as resolved.
Outdated
if (factory is not null)
{
return factory.GenerateValue(type, parameters);
}

return inner.GenerateValue(type, parameters);
}
}
}
10 changes: 10 additions & 0 deletions Source/Mockolate/MockBehaviorExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;

namespace Mockolate;

/// <summary>
Expand Down Expand Up @@ -34,5 +36,13 @@ public MockBehavior ThrowingWhenNotSetup(bool throwWhenNotSetup = true)
{
ThrowWhenNotSetup = throwWhenNotSetup,
};

/// <summary>
/// Uses the given <paramref name="factory" /> to create default values for <typeparamref name="T" />.
/// </summary>
public MockBehavior WithDefaultValueFor<T>(Func<T> factory)
=> mockBehavior.WithDefaultValueFor(new DefaultValueFactory(
t => t == typeof(T),
(_, _) => factory()));
}
}
9 changes: 9 additions & 0 deletions Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ namespace Mockolate
public object?[] Parameters { get; init; }
}
}
public class DefaultValueFactory
{
protected DefaultValueFactory() { }
public DefaultValueFactory(System.Func<System.Type, bool> predicate, System.Func<System.Type, object?[], object?> generator) { }
public virtual bool CanGenerateValue(System.Type type) { }
public virtual object? GenerateValue(System.Type type, params object?[] parameters) { }
}
public interface IDefaultValueGenerator
{
object? GenerateValue(System.Type type, params object?[] parameters);
Expand Down Expand Up @@ -90,13 +97,15 @@ namespace Mockolate
public Mockolate.MockBehavior Initialize<T>(params System.Action<int, Mockolate.Setup.IMockSetup<T>>[] setups) { }
public Mockolate.MockBehavior UseConstructorParametersFor<T>(System.Func<object?[]> parameters) { }
public Mockolate.MockBehavior UseConstructorParametersFor<T>(params object?[] parameters) { }
public Mockolate.MockBehavior WithDefaultValueFor(params Mockolate.DefaultValueFactory[] defaultValueFactories) { }
}
public static class MockBehaviorExtensions
{
extension(Mockolate.MockBehavior mockBehavior)
{
public Mockolate.MockBehavior SkippingBaseClass(bool skipBaseClass = true) { }
public Mockolate.MockBehavior ThrowingWhenNotSetup(bool throwWhenNotSetup = true) { }
public Mockolate.MockBehavior WithDefaultValueFor<T>(System.Func<T> factory) { }
}
}
public static class MockExtensions
Expand Down
9 changes: 9 additions & 0 deletions Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ namespace Mockolate
public object?[] Parameters { get; init; }
}
}
public class DefaultValueFactory
{
protected DefaultValueFactory() { }
public DefaultValueFactory(System.Func<System.Type, bool> predicate, System.Func<System.Type, object?[], object?> generator) { }
public virtual bool CanGenerateValue(System.Type type) { }
public virtual object? GenerateValue(System.Type type, params object?[] parameters) { }
}
public interface IDefaultValueGenerator
{
object? GenerateValue(System.Type type, params object?[] parameters);
Expand Down Expand Up @@ -89,13 +96,15 @@ namespace Mockolate
public Mockolate.MockBehavior Initialize<T>(params System.Action<int, Mockolate.Setup.IMockSetup<T>>[] setups) { }
public Mockolate.MockBehavior UseConstructorParametersFor<T>(System.Func<object?[]> parameters) { }
public Mockolate.MockBehavior UseConstructorParametersFor<T>(params object?[] parameters) { }
public Mockolate.MockBehavior WithDefaultValueFor(params Mockolate.DefaultValueFactory[] defaultValueFactories) { }
}
public static class MockBehaviorExtensions
{
extension(Mockolate.MockBehavior mockBehavior)
{
public Mockolate.MockBehavior SkippingBaseClass(bool skipBaseClass = true) { }
public Mockolate.MockBehavior ThrowingWhenNotSetup(bool throwWhenNotSetup = true) { }
public Mockolate.MockBehavior WithDefaultValueFor<T>(System.Func<T> factory) { }
}
}
public static class MockExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ namespace Mockolate
public object?[] Parameters { get; init; }
}
}
public class DefaultValueFactory
{
protected DefaultValueFactory() { }
public DefaultValueFactory(System.Func<System.Type, bool> predicate, System.Func<System.Type, object?[], object?> generator) { }
public virtual bool CanGenerateValue(System.Type type) { }
public virtual object? GenerateValue(System.Type type, params object?[] parameters) { }
}
public interface IDefaultValueGenerator
{
object? GenerateValue(System.Type type, params object?[] parameters);
Expand Down Expand Up @@ -84,13 +91,15 @@ namespace Mockolate
public Mockolate.MockBehavior Initialize<T>(params System.Action<int, Mockolate.Setup.IMockSetup<T>>[] setups) { }
public Mockolate.MockBehavior UseConstructorParametersFor<T>(System.Func<object?[]> parameters) { }
public Mockolate.MockBehavior UseConstructorParametersFor<T>(params object?[] parameters) { }
public Mockolate.MockBehavior WithDefaultValueFor(params Mockolate.DefaultValueFactory[] defaultValueFactories) { }
}
public static class MockBehaviorExtensions
{
extension(Mockolate.MockBehavior mockBehavior)
{
public Mockolate.MockBehavior SkippingBaseClass(bool skipBaseClass = true) { }
public Mockolate.MockBehavior ThrowingWhenNotSetup(bool throwWhenNotSetup = true) { }
public Mockolate.MockBehavior WithDefaultValueFor<T>(System.Func<T> factory) { }
}
}
public static class MockExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,23 @@ public sealed partial class MockBehaviorTests
{
public sealed class UseConstructorParametersForTests
{
[Fact]
public async Task WithExplicitConstructorParameters_ShouldIgnoreConstructorParametersFromBehavior()
{
MockBehavior behavior = MockBehavior.Default
.UseConstructorParametersFor<MyServiceWithMultipleConstructors>(() => [5,]);

MyServiceWithMultipleConstructors mock
= Mock.Create<MyServiceWithMultipleConstructors>(BaseClass.WithConstructorParameters(7), behavior);

int value = mock.Value;

await That(value).IsEqualTo(7);
}

[Fact]
public async Task
UseConstructorParametersFor_WithExplicitParameters_ShouldUseConstructorParametersFromBehavior()
WithExplicitParameters_ShouldUseConstructorParametersFromBehavior()
{
MockBehavior behavior = MockBehavior.Default
.UseConstructorParametersFor<MyServiceWithMultipleConstructors>(5);
Expand All @@ -24,7 +38,7 @@ MyServiceWithMultipleConstructors mockWithCustomBehavior
}

[Fact]
public async Task UseConstructorParametersFor_WithPredicate_ShouldUseConstructorParametersFromBehavior()
public async Task WithPredicate_ShouldUseConstructorParametersFromBehavior()
{
MockBehavior behavior = MockBehavior.Default
.UseConstructorParametersFor<MyServiceWithMultipleConstructors>(() => [5,]);
Expand All @@ -41,20 +55,6 @@ MyServiceWithMultipleConstructors mockWithCustomBehavior
await That(valueWithCustomBehavior).IsEqualTo(5);
}

[Fact]
public async Task WithExplicitConstructorParameters_ShouldIgnoreConstructorParametersFromBehavior()
{
MockBehavior behavior = MockBehavior.Default
.UseConstructorParametersFor<MyServiceWithMultipleConstructors>(() => [5,]);

MyServiceWithMultipleConstructors mock
= Mock.Create<MyServiceWithMultipleConstructors>(BaseClass.WithConstructorParameters(7), behavior);

int value = mock.Value;

await That(value).IsEqualTo(7);
}

internal class MyServiceWithMultipleConstructors
{
public MyServiceWithMultipleConstructors()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace Mockolate.Tests;

public sealed partial class MockBehaviorTests
{
public sealed class WithDefaultValueForTests
{
[Fact]
public async Task MultipleTypedFactories_ShouldSupportAll()
{
MockBehavior behavior = MockBehavior.Default
.WithDefaultValueFor(() => new MySpecialValue(5))
.WithDefaultValueFor(() => new MyInheritedSpecialValue(6, 7));

IMyServiceForMySpecialValue mockWithBehavior
= Mock.Create<IMyServiceForMySpecialValue>(behavior);

await That(mockWithBehavior.Value.Value).IsEqualTo(5);
await That(mockWithBehavior.InheritedValue)
.For(x => x.Value, it => it.IsEqualTo(6)).And
.For(x => x.OtherValue, it => it.IsEqualTo(7));
}

[Fact]
public async Task WithExplicitDefaultValueFactory_ShouldApplyThisFactory()
{
DefaultValueFactory defaultValueFactory = new(
t => typeof(MySpecialValue).IsAssignableFrom(t),
(_, _) => new MyInheritedSpecialValue(6, 7));
MockBehavior behavior = MockBehavior.Default
.WithDefaultValueFor(defaultValueFactory);

IMyServiceForMySpecialValue mockWithBehavior
= Mock.Create<IMyServiceForMySpecialValue>(behavior);

await That(mockWithBehavior.Value.Value).IsEqualTo(6);
await That(mockWithBehavior.InheritedValue)
.For(x => x.Value, it => it.IsEqualTo(6)).And
.For(x => x.OtherValue, it => it.IsEqualTo(7));
}

[Fact]
public async Task WithTypedFactory_ShouldRequireExactTypeMatch()
{
MockBehavior behavior1 = MockBehavior.Default
.WithDefaultValueFor(() => new MySpecialValue(5));
MockBehavior behavior2 = MockBehavior.Default
.WithDefaultValueFor(() => new MyInheritedSpecialValue(6, 7));

IMyServiceForMySpecialValue mockWithBehavior1
= Mock.Create<IMyServiceForMySpecialValue>(behavior1);
IMyServiceForMySpecialValue mockWithBehavior2
= Mock.Create<IMyServiceForMySpecialValue>(behavior2);

await That(mockWithBehavior1.InheritedValue).IsNull();
await That(mockWithBehavior1.Value.Value).IsEqualTo(5);
await That(mockWithBehavior2.InheritedValue)
.For(x => x.Value, it => it.IsEqualTo(6)).And
.For(x => x.OtherValue, it => it.IsEqualTo(7));
await That(mockWithBehavior2.Value).IsNull();
}

[Fact]
public async Task WithTypedFactory_ShouldUseDefaultValueFactoryFromBehavior()
{
MockBehavior behavior = MockBehavior.Default
.WithDefaultValueFor(() => new MySpecialValue(5));

IMyServiceForMySpecialValue mockWithDefaultBehavior
= Mock.Create<IMyServiceForMySpecialValue>();
IMyServiceForMySpecialValue mockWithCustomBehavior
= Mock.Create<IMyServiceForMySpecialValue>(behavior);

await That(mockWithDefaultBehavior.Value).IsNull();
await That(mockWithCustomBehavior.Value.Value).IsEqualTo(5);
}

internal class MySpecialValue(int value)
{
public int Value { get; } = value;
}

internal class MyInheritedSpecialValue(int value, int otherValue) : MySpecialValue(value)
{
public int OtherValue { get; } = otherValue;
}

internal interface IMyServiceForMySpecialValue
{
MySpecialValue Value { get; }
MyInheritedSpecialValue InheritedValue { get; }
}
}
}
Loading