-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Feature: Extend PartsOf to mock non-virtual methods implementing an i… #700
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
namespace NSubstitute.Exceptions; | ||
|
||
public abstract class TypeForwardingException(string message) : SubstituteException(message) | ||
{ | ||
} | ||
|
||
public sealed class CanNotForwardCallsToClassNotImplementingInterfaceException(Type type) : TypeForwardingException(DescribeProblem(type)) | ||
{ | ||
private static string DescribeProblem(Type type) | ||
{ | ||
return string.Format("The provided class '{0}' doesn't implement all requested interfaces. ", type.Name); | ||
} | ||
} | ||
|
||
public sealed class CanNotForwardCallsToAbstractClassException(Type type) : TypeForwardingException(DescribeProblem(type)) | ||
{ | ||
private static string DescribeProblem(Type type) | ||
{ | ||
return string.Format("The provided class '{0}' is abstract. ", type.Name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick (non-blocking): trailing space: |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,7 @@ public virtual ICall Map(IInvocation castleInvocation) | |
Func<object>? baseMethod = null; | ||
if (castleInvocation.InvocationTarget != null && | ||
castleInvocation.MethodInvocationTarget.IsVirtual && | ||
!castleInvocation.MethodInvocationTarget.IsAbstract && | ||
!castleInvocation.MethodInvocationTarget.IsFinal) | ||
!castleInvocation.MethodInvocationTarget.IsAbstract) | ||
Comment on lines
-14
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: dropping "not final" constraint here is required to support call forwarding? |
||
{ | ||
baseMethod = CreateBaseResultInvocation(castleInvocation); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,4 +89,24 @@ public static T ForPartsOf<T>(params object[] constructorArguments) | |
var substituteFactory = SubstitutionContext.Current.SubstituteFactory; | ||
return (T)substituteFactory.CreatePartial([typeof(T)], constructorArguments); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a proxy for a class that implements an interface, forwarding methods and properties to an instance of the class, effectively mimicking a real instance. | ||
dtchepak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Both the interface and the class must be provided as parameters. | ||
/// The proxy will log calls made to the interface members and delegate them to an instance of the class. Specific members can be substituted | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick (non-blocking): "will log calls" -> "will intercept calls" |
||
/// by using <see cref="WhenCalled{T}.DoNotCallBase()">When(() => call).DoNotCallBase()</see> or by | ||
/// <see cref="SubstituteExtensions.Returns{T}(T,T,T[])">setting a value to return value</see> for that member. | ||
/// This extension supports sealed classes and non-virtual members, with some limitations. Since the substituted method is non-virtual, internal calls within the object will invoke the original implementation and will not be logged. | ||
/// </summary> | ||
/// <typeparam name="TInterface">The interface the substitute will implement.</typeparam> | ||
/// <typeparam name="TClass">The class type implementing the interface. Must be a class; not a delegate or interface. </typeparam> | ||
/// <param name="constructorArguments"></param> | ||
/// <returns>An object implementing the selected interface. Calls will be forwarded to the actuall methods, but allows parts to be selectively | ||
/// overridden via `Returns` and `When..DoNotCallBase`.</returns> | ||
public static TInterface ForTypeForwardingTo<TInterface, TClass>(params object[] constructorArguments) | ||
where TInterface : class | ||
{ | ||
var substituteFactory = SubstitutionContext.Current.SubstituteFactory; | ||
return (TInterface)substituteFactory.CreatePartial([typeof(TInterface), typeof(TClass)], constructorArguments); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (non-blocking): for a future change, I think it would be helpful for this to explain which of the requested interfaces has not been implemented. If we see "Class 'A' does not implement all requested interfaces. To support call forwarding 'A' must implement 'B', 'C', and 'D'." then it gives us a good hint on how to fix the exception.