diff --git a/Tests/Mockolate.Internal.Tests/Setup/IndexerSetupTests.cs b/Tests/Mockolate.Internal.Tests/Setup/IndexerSetupTests.cs index 10bb29e1..eb78c574 100644 --- a/Tests/Mockolate.Internal.Tests/Setup/IndexerSetupTests.cs +++ b/Tests/Mockolate.Internal.Tests/Setup/IndexerSetupTests.cs @@ -57,7 +57,7 @@ public async Task ExecuteGetterCallback_WhenTypesAndNumberMatch_ShouldExecute() indexerSetup.OnGet.Do(() => { callCount++; }); IndexerGetterAccess access = new(1); - string result = indexerSetup.DoGetResult(access, "foo"); + indexerSetup.DoGetResult(access, "foo"); await That(callCount).IsEqualTo(1); } @@ -233,11 +233,24 @@ public async Task ExecuteGetterCallback_WhenTypesAndNumberMatch_ShouldExecute() indexerSetup.OnGet.Do(() => { callCount++; }); IndexerGetterAccess access = new(1, 2); - string result = indexerSetup.DoGetResult(access, "foo"); + indexerSetup.DoGetResult(access, "foo"); await That(callCount).IsEqualTo(1); } + [Fact] + public async Task ExecuteSetterCallback_WhenAssignedValueDoesNotCastToTValue_ShouldNotExecute() + { + int callCount = 0; + MyIndexerSetup indexerSetup = new(); + indexerSetup.OnSet.Do(() => { callCount++; }); + IndexerSetterAccess access = new(1, 2, "bar"); + + indexerSetup.DoSetResult(access, 2L); + + await That(callCount).IsEqualTo(0); + } + [Fact] public async Task ExecuteSetterCallback_WhenGenericTypeDoesNotMatch_ShouldNotExecute() { @@ -316,6 +329,33 @@ public async Task GetResult_WithBaseValue_StoresComputedValueForLaterLookup() await That(stored).IsEqualTo("base"); } + [Fact] + public async Task GetResult_WithFuncGenerator_AndInitialization_ShouldUseInitializationValue() + { + IndexerSetup setup = new( + new MockRegistry(MockBehavior.Default, new FastMockInteractions(0)), + (IParameterMatch)It.IsAny(), + (IParameterMatch)It.IsAny()); + setup.InitializeWith((p1, p2) => $"{p1}-{p2}"); + IndexerValueStorage storage = new(); + IndexerGetterAccess access1 = new(7, 9) + { + Storage = storage, + }; + + string result = setup.GetResult(access1, MockBehavior.Default, () => "fallback"); + + IndexerGetterAccess access2 = new(7, 9) + { + Storage = storage, + }; + bool found = access2.TryFindStoredValue(out string stored); + + await That(result).IsEqualTo("7-9"); + await That(found).IsTrue(); + await That(stored).IsEqualTo("7-9"); + } + private sealed class MyIndexerSetup() : IndexerSetup( new MockRegistry(MockBehavior.Default, new FastMockInteractions(0)), @@ -392,11 +432,24 @@ public async Task ExecuteGetterCallback_WhenTypesAndNumberMatch_ShouldExecute() indexerSetup.OnGet.Do(() => { callCount++; }); IndexerGetterAccess access = new(1, 2, 3); - string result = indexerSetup.DoGetResult(access, "foo"); + indexerSetup.DoGetResult(access, "foo"); await That(callCount).IsEqualTo(1); } + [Fact] + public async Task ExecuteSetterCallback_WhenAssignedValueDoesNotCastToTValue_ShouldNotExecute() + { + int callCount = 0; + MyIndexerSetup indexerSetup = new(); + indexerSetup.OnSet.Do(() => { callCount++; }); + IndexerSetterAccess access = new(1, 2, 3, "bar"); + + indexerSetup.DoSetResult(access, 2L); + + await That(callCount).IsEqualTo(0); + } + [Fact] public async Task ExecuteSetterCallback_WhenGenericTypeDoesNotMatch_ShouldNotExecute() { @@ -478,6 +531,34 @@ public async Task GetResult_WithBaseValue_StoresComputedValueForLaterLookup() await That(stored).IsEqualTo("base"); } + [Fact] + public async Task GetResult_WithFuncGenerator_AndInitialization_ShouldUseInitializationValue() + { + IndexerSetup setup = new( + new MockRegistry(MockBehavior.Default, new FastMockInteractions(0)), + (IParameterMatch)It.IsAny(), + (IParameterMatch)It.IsAny(), + (IParameterMatch)It.IsAny()); + setup.InitializeWith((p1, p2, p3) => $"{p1}-{p2}-{p3}"); + IndexerValueStorage storage = new(); + IndexerGetterAccess access1 = new(7, 8, 9) + { + Storage = storage, + }; + + string result = setup.GetResult(access1, MockBehavior.Default, () => "fallback"); + + IndexerGetterAccess access2 = new(7, 8, 9) + { + Storage = storage, + }; + bool found = access2.TryFindStoredValue(out string stored); + + await That(result).IsEqualTo("7-8-9"); + await That(found).IsTrue(); + await That(stored).IsEqualTo("7-8-9"); + } + private sealed class MyIndexerSetup() : IndexerSetup( new MockRegistry(MockBehavior.Default, new FastMockInteractions(0)), @@ -558,11 +639,25 @@ public async Task ExecuteGetterCallback_WhenTypesAndNumberMatch_ShouldExecute() IndexerGetterAccess access = new(1, 2, 3, 4); - string result = indexerSetup.DoGetResult(access, "foo"); + indexerSetup.DoGetResult(access, "foo"); await That(callCount).IsEqualTo(1); } + [Fact] + public async Task ExecuteSetterCallback_WhenAssignedValueDoesNotCastToTValue_ShouldNotExecute() + { + int callCount = 0; + MyIndexerSetup indexerSetup = new(); + indexerSetup.OnSet.Do(() => { callCount++; }); + IndexerSetterAccess access = + new(1, 2, 3, 4, "bar"); + + indexerSetup.DoSetResult(access, 2L); + + await That(callCount).IsEqualTo(0); + } + [Fact] public async Task ExecuteSetterCallback_WhenGenericTypeDoesNotMatch_ShouldNotExecute() { @@ -649,6 +744,35 @@ public async Task GetResult_WithBaseValue_StoresComputedValueForLaterLookup() await That(stored).IsEqualTo("base"); } + [Fact] + public async Task GetResult_WithFuncGenerator_AndInitialization_ShouldUseInitializationValue() + { + IndexerSetup setup = new( + new MockRegistry(MockBehavior.Default, new FastMockInteractions(0)), + (IParameterMatch)It.IsAny(), + (IParameterMatch)It.IsAny(), + (IParameterMatch)It.IsAny(), + (IParameterMatch)It.IsAny()); + setup.InitializeWith((p1, p2, p3, p4) => $"{p1}-{p2}-{p3}-{p4}"); + IndexerValueStorage storage = new(); + IndexerGetterAccess access1 = new(6, 7, 8, 9) + { + Storage = storage, + }; + + string result = setup.GetResult(access1, MockBehavior.Default, () => "fallback"); + + IndexerGetterAccess access2 = new(6, 7, 8, 9) + { + Storage = storage, + }; + bool found = access2.TryFindStoredValue(out string stored); + + await That(result).IsEqualTo("6-7-8-9"); + await That(found).IsTrue(); + await That(stored).IsEqualTo("6-7-8-9"); + } + private sealed class MyIndexerSetup() : IndexerSetup( new MockRegistry(MockBehavior.Default, new FastMockInteractions(0)), @@ -731,7 +855,7 @@ public async Task ExecuteGetterCallback_WhenTypesAndNumberMatch_ShouldExecute() IndexerGetterAccess access = new(1, 2, 3, 4, 5); - string result = indexerSetup.DoGetResult(access, "foo"); + indexerSetup.DoGetResult(access, "foo"); await That(callCount).IsEqualTo(1); } diff --git a/Tests/Mockolate.Internal.Tests/Setup/PropertySetupTests.cs b/Tests/Mockolate.Internal.Tests/Setup/PropertySetupTests.cs index da201049..b6fca657 100644 --- a/Tests/Mockolate.Internal.Tests/Setup/PropertySetupTests.cs +++ b/Tests/Mockolate.Internal.Tests/Setup/PropertySetupTests.cs @@ -1,3 +1,4 @@ +using Mockolate.Exceptions; using Mockolate.Internal.Tests.TestHelpers; using Mockolate.Setup; @@ -31,6 +32,48 @@ public async Task DefaultInvokeGetter_WhenRequestedTypeDiffersFromBackingType_Sh await That(value).IsEqualTo(99f); } + [Fact] + public async Task DefaultInvokeSetter_WhenValueIsNullAndUnderlyingTypeIsNullable_ShouldStoreDefault() + { + PropertySetup.Default setup = new("p", 5); + IInteractivePropertySetup interactive = setup; + + interactive.InvokeSetter(null, null, MockBehavior.Default); + + int? value = interactive.InvokeGetter(null, MockBehavior.Default, () => 42); + await That(value).IsNull(); + } + + [Fact] + public async Task DefaultInvokeSetter_WhenValueIsNullButUnderlyingTypeIsNonNullable_ShouldThrow() + { + PropertySetup.Default setup = new("p", 5); + IInteractivePropertySetup interactive = setup; + + void Act() + { + interactive.InvokeSetter(null, null, MockBehavior.Default); + } + + await That(Act).Throws() + .WithMessage("*int*").AsWildcard(); + } + + [Fact] + public async Task DefaultInvokeSetter_WhenValueTypeMismatch_ShouldThrowWithFormattedMessage() + { + PropertySetup.Default setup = new("p", 5); + IInteractivePropertySetup interactive = setup; + + void Act() + { + interactive.InvokeSetter(null, "string-value", MockBehavior.Default); + } + + await That(Act).Throws() + .WithMessage("*'int'*'string'*").AsWildcard(); + } + [Fact] public async Task UserInitializeWith_SecondCall_ShouldNotOverwriteValue() { diff --git a/Tests/Mockolate.Internal.Tests/Verify/TypedVerifyFastPathTests.cs b/Tests/Mockolate.Internal.Tests/Verify/TypedVerifyFastPathTests.cs index 910425e0..4a26c3e0 100644 --- a/Tests/Mockolate.Internal.Tests/Verify/TypedVerifyFastPathTests.cs +++ b/Tests/Mockolate.Internal.Tests/Verify/TypedVerifyFastPathTests.cs @@ -7,6 +7,129 @@ namespace Mockolate.Internal.Tests.Verify; public class TypedVerifyFastPathTests { + [Fact] + public async Task IndexerGot_WithoutBuffer_ProducesParametersDescriptionInExpectation() + { + FastMockInteractions store = new(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.IndexerGot( + new object(), 5, + static _ => true, + () => "(5)"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("got indexer (5)"); + } + + [Fact] + public async Task IndexerGotTyped_WithBuffer_ProducesParametersDescriptionInExpectation() + { + FastMockInteractions store = new(1); + store.InstallIndexerGetter(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.IndexerGotTyped( + new object(), 0, + (IParameterMatch)It.Is(5), + () => "(5)"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("got indexer (5)"); + } + + [Fact] + public async Task IndexerSet_WithoutBuffer_ProducesParametersDescriptionInExpectation() + { + FastMockInteractions store = new(0); + MockRegistry registry = new(MockBehavior.Default, store); + + IParameterMatch value = (IParameterMatch)It.Is(7); + VerificationResult result = registry.IndexerSet( + new object(), 5, + static (_, _) => true, + value, + () => "(5)"); + + await That(((IVerificationResult)result).Expectation).Contains("set indexer (5)"); + await That(((IVerificationResult)result).Expectation).Contains(value.ToString()!); + } + + [Fact] + public async Task IndexerSetTyped_WithBuffer_ProducesParametersDescriptionInExpectation() + { + FastMockInteractions store = new(1); + store.InstallIndexerSetter(0); + MockRegistry registry = new(MockBehavior.Default, store); + + IParameterMatch value = (IParameterMatch)It.Is("v"); + VerificationResult result = registry.IndexerSetTyped( + new object(), 0, + (IParameterMatch)It.Is(5), + value, + () => "(5)"); + + await That(((IVerificationResult)result).Expectation).Contains("set indexer (5)"); + await That(((IVerificationResult)result).Expectation).Contains(value.ToString()!); + } + + [Fact] + public async Task SubscribedToTyped_WithBuffer_ProducesEventNameInExpectation() + { + FastMockInteractions store = new(1); + store.InstallEventSubscribe(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.SubscribedToTyped(new object(), 0, "OnFoo"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("subscribed to event OnFoo"); + } + + [Fact] + public async Task SubscribedToTyped_WithMemberIdButNoBuffer_FallsBackToStringKeyedPath() + { + FastMockInteractions store = new(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.SubscribedToTyped(new object(), 5, "OnFoo"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("subscribed to event OnFoo"); + } + + [Fact] + public async Task UnsubscribedFromTyped_WithBuffer_ProducesEventNameInExpectation() + { + FastMockInteractions store = new(1); + store.InstallEventUnsubscribe(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.UnsubscribedFromTyped(new object(), 0, "OnFoo"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("unsubscribed from event OnFoo"); + } + + [Fact] + public async Task UnsubscribedFromTyped_WithMemberIdButNoBuffer_FallsBackToStringKeyedPath() + { + FastMockInteractions store = new(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.UnsubscribedFromTyped(new object(), 5, "OnFoo"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("unsubscribed from event OnFoo"); + } + + [Fact] + public async Task UnsubscribedFromTyped_WithMemberIdButNonMatchingBufferKind_FallsBackToStringKeyedPath() + { + FastMockInteractions store = new(1); + store.InstallMethod(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.UnsubscribedFromTyped(new object(), 0, "OnFoo"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("unsubscribed from event OnFoo"); + } + + [Fact] public async Task VerifyMethod0_TypedFastPath_ShouldCount() { @@ -18,7 +141,7 @@ public async Task VerifyMethod0_TypedFastPath_ShouldCount() buffer.Append("Foo"); buffer.Append("Foo"); - registry.VerifyMethod(new object(), 0, "Foo", () => "Foo()").Twice(); + registry.VerifyMethod(new object(), 0, "Foo", () => "Foo()").Twice(); await That(true).IsTrue(); } @@ -34,7 +157,7 @@ public async Task VerifyMethod1_TypedFastPath_FailsWithExpectedMessage() buffer.Append("Foo", 1); buffer.Append("Foo", 1); - await That(() => registry.VerifyMethod(new object(), 0, "Foo", + await That(() => registry.VerifyMethod(new object(), 0, "Foo", (IParameterMatch)It.Is(1), () => "Foo(1)").Once()) .Throws(); } @@ -53,7 +176,7 @@ public async Task VerifyMethod1_TypedFastPath_ShouldHonorMatcher() registry.VerifyMethod(new object(), 0, "Foo", (IParameterMatch)It.Is(1), () => "Foo(1)").Twice(); - registry.VerifyMethod(new object(), 0, "Foo", + registry.VerifyMethod(new object(), 0, "Foo", (IParameterMatch)It.IsAny(), () => "Foo(*)").Exactly(3); await That(true).IsTrue(); @@ -83,4 +206,63 @@ public async Task VerifyMethod2_TypedFastPath_AnyParameters_UsesCountAll() await That(true).IsTrue(); } + + [Fact] + public async Task VerifyProperty_WithMemberIdButNoBuffer_FallsBackToStringKeyedPath() + { + FastMockInteractions store = new(0); + MockRegistry registry = new(MockBehavior.Default, store); + IMockInteractions interactions = store; + + interactions.RegisterInteraction(new PropertyGetterAccess("P")); + interactions.RegisterInteraction(new PropertyGetterAccess("P")); + + VerificationResult result = registry.VerifyProperty(new object(), 5, "P"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("got property P"); + ((IVerificationResult)result).Verify(arr => arr.Length == 2); + } + + [Fact] + public async Task VerifyPropertySetter_WithMemberIdButNoBuffer_FallsBackToStringKeyedPath() + { + FastMockInteractions store = new(0); + MockRegistry registry = new(MockBehavior.Default, store); + IMockInteractions interactions = store; + + interactions.RegisterInteraction(new PropertySetterAccess("P", 1)); + interactions.RegisterInteraction(new PropertySetterAccess("P", 2)); + + VerificationResult result = registry.VerifyProperty( + new object(), 5, "P", (IParameterMatch)It.IsAny()); + + await That(((IVerificationResult)result).Expectation).Contains("set property P"); + ((IVerificationResult)result).Verify(arr => arr.Length == 2); + } + + [Fact] + public async Task VerifyPropertyTyped_Getter_WithBuffer_ProducesPropertyNameInExpectation() + { + FastMockInteractions store = new(1); + store.InstallPropertyGetter(0); + MockRegistry registry = new(MockBehavior.Default, store); + + VerificationResult result = registry.VerifyPropertyTyped(new object(), 0, "P"); + + await That(((IVerificationResult)result).Expectation).IsEqualTo("got property P"); + } + + [Fact] + public async Task VerifyPropertyTyped_Setter_WithBuffer_ProducesPropertyNameInExpectation() + { + FastMockInteractions store = new(1); + store.InstallPropertySetter(0); + MockRegistry registry = new(MockBehavior.Default, store); + + IParameterMatch value = (IParameterMatch)It.Is(42); + VerificationResult result = registry.VerifyPropertyTyped(new object(), 0, "P", value); + + await That(((IVerificationResult)result).Expectation).Contains("set property P"); + await That(((IVerificationResult)result).Expectation).Contains(value.ToString()!); + } } diff --git a/Tests/Mockolate.Tests/SetupExtensionsTests.cs b/Tests/Mockolate.Tests/SetupExtensionsTests.cs index 81da6565..5a75ea54 100644 --- a/Tests/Mockolate.Tests/SetupExtensionsTests.cs +++ b/Tests/Mockolate.Tests/SetupExtensionsTests.cs @@ -100,6 +100,21 @@ public async Task OnlyOnce_WithFor_ShouldInvokeTheCallbackOnlyForTimes() await That(values).IsEqualTo([1, 1, 1,]); } + + [Fact] + public async Task OnSet_OnlyOnce_WithFor_ShouldInvokeTheCallbackOnlyForTimes() + { + List values = []; + ISetupExtensionsTestService sut = ISetupExtensionsTestService.CreateMock(); + sut.Mock.Setup.MyProperty.OnSet.Do(() => values.Add(1)).For(3).OnlyOnce(); + + for (int i = 0; i < 10; i++) + { + sut.MyProperty = i; + } + + await That(values).IsEqualTo([1, 1, 1,]); + } } public sealed class IndexerSetupReturnWhenBuilderTests @@ -680,6 +695,84 @@ public async Task OnlyOnce_With5Parameters_WithFor_ShouldInvokeTheCallbackOnlyFo await That(values).IsEqualTo([1, 1, 1,]); } + + [Fact] + public async Task OnSet_OnlyOnce_With1Parameter_WithFor_ShouldInvokeTheCallbackOnlyForTimes() + { + List values = []; + ISetupExtensionsTestService sut = ISetupExtensionsTestService.CreateMock(); + sut.Mock.Setup[It.IsAny()].OnSet.Do(() => values.Add(1)).For(3).OnlyOnce(); + + for (int i = 0; i < 10; i++) + { + sut[10] = i; + } + + await That(values).IsEqualTo([1, 1, 1,]); + } + + [Fact] + public async Task OnSet_OnlyOnce_With2Parameters_WithFor_ShouldInvokeTheCallbackOnlyForTimes() + { + List values = []; + ISetupExtensionsTestService sut = ISetupExtensionsTestService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny()].OnSet.Do(() => values.Add(1)).For(3).OnlyOnce(); + + for (int i = 0; i < 10; i++) + { + sut[10, 20] = i; + } + + await That(values).IsEqualTo([1, 1, 1,]); + } + + [Fact] + public async Task OnSet_OnlyOnce_With3Parameters_WithFor_ShouldInvokeTheCallbackOnlyForTimes() + { + List values = []; + ISetupExtensionsTestService sut = ISetupExtensionsTestService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny()].OnSet.Do(() => values.Add(1)) + .For(3).OnlyOnce(); + + for (int i = 0; i < 10; i++) + { + sut[10, 20, 30] = i; + } + + await That(values).IsEqualTo([1, 1, 1,]); + } + + [Fact] + public async Task OnSet_OnlyOnce_With4Parameters_WithFor_ShouldInvokeTheCallbackOnlyForTimes() + { + List values = []; + ISetupExtensionsTestService sut = ISetupExtensionsTestService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnSet.Do(() => values.Add(1)).For(3).OnlyOnce(); + + for (int i = 0; i < 10; i++) + { + sut[10, 20, 30, 40] = i; + } + + await That(values).IsEqualTo([1, 1, 1,]); + } + + [Fact] + public async Task OnSet_OnlyOnce_With5Parameters_WithFor_ShouldInvokeTheCallbackOnlyForTimes() + { + List values = []; + ISetupExtensionsTestService sut = ISetupExtensionsTestService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnSet.Do(() => values.Add(1)).For(3).OnlyOnce(); + + for (int i = 0; i < 10; i++) + { + sut[10, 20, 30, 40, 50] = i; + } + + await That(values).IsEqualTo([1, 1, 1,]); + } } public sealed class ReturnMethodSetupReturnWhenBuilderTests