Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to mock protected methods with and without return value #845

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Jason31569
Copy link

Closes #800

@Jason31569 Jason31569 changed the title Ability to mock protected methods with and without return types (void) Ability to mock protected methods with and without return value Nov 20, 2024
@dtchepak dtchepak requested a review from Romfos November 30, 2024 08:22
Copy link
Member

@dtchepak dtchepak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! WDYT @Romfos ?

Comment on lines +22 to +31
public static object Protected<T>(this T obj, string methodName, params object[] args) where T : class
{
if (obj == null) { throw new ArgumentNullException(nameof(obj), "Cannot mock null object"); }
if (string.IsNullOrWhiteSpace(methodName)) { throw new ArgumentException("Must provide valid protected method name to mock", nameof(methodName)); }

IList<IArgumentSpecification> argTypes = SubstitutionContext.Current.ThreadContext.PeekAllArgumentSpecifications();
MethodInfo mthdInfo = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, argTypes.Select(x => x.ForType).ToArray(), null);

if (mthdInfo == null) { throw new Exception($"Method {methodName} not found"); }
if (!mthdInfo.IsVirtual) { throw new Exception($"Method {methodName} is not virtual"); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (nitpick): ideally should use a custom exception(subclassing SubstituteException) with details on what they should do to fix the issue. (see NSubstitute.Exceptions for examples.)

For obj == null, we have NullSubstituteReferenceException. Should also ensure receiver is a substitute otherwise throw NotASubstituteException.

For mthdInfo == null, maybe include arg types checked, something like "No method found with signature Foo(Int, String) on IMySubstitute. Check the method name and arguments are correct."

For non-virtual, can probably use a summary of the warning given in the documentation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, this makes sense

public IList<IArgumentSpecification> PeekAllArgumentSpecifications()
{
var queue = _argumentSpecifications.Value;
if (queue == null) { throw new SubstituteInternalException("Argument specification queue is null."); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do you know under what circumstances this occurs? If it is expected can we just return EmptySpecifications in that case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh it doesn't look like it could ever be null. I decided to have/keep this in line with enqueue and dequeue methods in case it is something I failed to see

Should we change all 3 (along with enqueue, dequeue) methods to provide a consistent behavior?

/// <returns>WhenCalled&lt;T&gt;.</returns>
/// <exception cref="System.ArgumentNullException">Substitute - Cannot mock null object</exception>
/// <exception cref="System.ArgumentException">Must provide valid protected method name to mock - methodName</exception>
public static WhenCalled<T> When<T>(this T obj, string methodName, params object[] args) where T : class
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: maybe this should be WhenProtected or something to more easily distinguish from When? What do you think? (I'm genuinely not sure, just want something natural for people when both are in scope)

Copy link
Author

@Jason31569 Jason31569 Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My ocd prefers seeing a bunch of When methods for configuring methods with no return value, but I agree it may be more intuitive to some people when the method screams out "Protected", maybe we need a tie breaker if we're both on the fence?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to mock requests from Protected methods in sequences
2 participants