diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.cs index 7bc9df14..daaa14cd 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.cs @@ -400,7 +400,7 @@ public T Create(BaseClass.ConstructorParameters constructorParameters, params /// Wraps a concrete instance with a mock proxy that intercepts and delegates method calls, /// supporting setup and verification on the wrapped instance. /// - /// Type to wrap, which can be an interface or a class. + /// Type to wrap, which must be an interface. /// The concrete instance to wrap. /// Optional setup actions to configure the mock. /// @@ -416,7 +416,7 @@ public static T Wrap(T instance, params Action>[] setups) throw new ArgumentNullException(nameof(instance)); } - ThrowIfNotMockable(typeof(T)); + ThrowIfNotWrappable(typeof(T)); return new MockGenerator().GetWrapped(instance, MockBehavior.Default, setups) ?? throw new MockException("Could not generate wrapped Mock. Did the source generator run correctly?"); @@ -429,7 +429,7 @@ public static T Wrap(T instance, params Action>[] setups) /// Wraps a concrete instance with a mock proxy that intercepts and delegates method calls, /// supporting setup and verification on the wrapped instance. /// - /// Type to wrap, which can be an interface or a class. + /// Type to wrap, which must be an interface. /// The concrete instance to wrap. /// The behavior settings for the mock. /// Optional setup actions to configure the mock. @@ -446,7 +446,7 @@ public static T Wrap(T instance, MockBehavior mockBehavior, params Action(instance, mockBehavior, setups) ?? throw new MockException("Could not generate wrapped Mock. Did the source generator run correctly?"); @@ -459,7 +459,15 @@ private static void ThrowIfNotMockable(Type type) { if (type.IsSealed && type.BaseType != typeof(MulticastDelegate)) { - throw new MockException($"The type '{type}' is sealed and therefore not mockable."); + throw new MockException($"Unable to mock type '{type.FullName ?? type.Name}'. The type is sealed and therefore not mockable."); + } + } + + private static void ThrowIfNotWrappable(Type type) + { + if (!type.IsInterface) + { + throw new MockException($"Unable to wrap type '{type.FullName ?? type.Name}'. When wrapping a concrete instance, only interfaces can be mocked."); } } """); diff --git a/Tests/Mockolate.Tests/MockTests.FactoryTests.cs b/Tests/Mockolate.Tests/MockTests.FactoryTests.cs index 1ea8409f..27813f39 100644 --- a/Tests/Mockolate.Tests/MockTests.FactoryTests.cs +++ b/Tests/Mockolate.Tests/MockTests.FactoryTests.cs @@ -21,7 +21,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -38,7 +38,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -55,7 +55,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -72,7 +72,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -89,7 +89,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -106,7 +106,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -125,7 +125,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -143,7 +143,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -161,7 +161,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -178,7 +178,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] diff --git a/Tests/Mockolate.Tests/MockTests.WrapTests.cs b/Tests/Mockolate.Tests/MockTests.WrapTests.cs index 6d2e8d27..274bd15a 100644 --- a/Tests/Mockolate.Tests/MockTests.WrapTests.cs +++ b/Tests/Mockolate.Tests/MockTests.WrapTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Mockolate.Exceptions; using Mockolate.Tests.TestHelpers; namespace Mockolate.Tests; @@ -118,6 +119,36 @@ public async Task Wrap_Property_ShouldDelegateToWrappedInstance() await That(myDispenser.TotalDispensed).IsEqualTo(12); } + [Fact] + public async Task Wrap_WithClass_ShouldThrowMockException() + { + MyChocolateDispenser instance = new(); + + void Act() + { + _ = Mock.Wrap(instance); + } + + await That(Act).Throws() + .WithMessage( + "Unable to wrap type 'Mockolate.Tests.MockTests+WrapTests+MyChocolateDispenser'. When wrapping a concrete instance, only interfaces can be mocked."); + } + + [Fact] + public async Task Wrap_WithDelegate_ShouldThrowMockException() + { + MyDelegate instance = () => { }; + + void Act() + { + _ = Mock.Wrap(instance); + } + + await That(Act).Throws() + .WithMessage( + "Unable to wrap type 'Mockolate.Tests.MockTests+WrapTests+MyDelegate'. When wrapping a concrete instance, only interfaces can be mocked."); + } + [Fact] public async Task Wrap_WithSetup_ShouldOverrideMethod() { @@ -170,5 +201,7 @@ public bool Dispense(string type, int amount) public event ChocolateDispensedDelegate? ChocolateDispensed; } + + public delegate void MyDelegate(); } } diff --git a/Tests/Mockolate.Tests/MockTests.cs b/Tests/Mockolate.Tests/MockTests.cs index 2be5ab5c..75d64885 100644 --- a/Tests/Mockolate.Tests/MockTests.cs +++ b/Tests/Mockolate.Tests/MockTests.cs @@ -80,7 +80,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -95,7 +95,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -110,7 +110,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -125,7 +125,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -140,7 +140,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -155,7 +155,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -170,7 +170,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -186,7 +186,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -202,7 +202,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -217,7 +217,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact] @@ -267,7 +267,7 @@ void Act() await That(Act).Throws() .WithMessage( - "The type 'Mockolate.Tests.MockTests+MySealedClass' is sealed and therefore not mockable."); + "Unable to mock type 'Mockolate.Tests.MockTests+MySealedClass'. The type is sealed and therefore not mockable."); } [Fact]