Skip to content

mock.Protected().Setup<int>("Foo") fails base implementation of Foo is hidden in the derived class - #1342

Closed
VladimirKhvostov wants to merge 2293 commits into
devlooped:mainfrom
VladimirKhvostov:main
Closed

VladimirKhvostov wants to merge 2293 commits into
devlooped:mainfrom
VladimirKhvostov:main

Conversation

@VladimirKhvostov

Copy link
Copy Markdown

This PR fixes an issue described in the #1341

stakx and others added 30 commits October 27, 2020 19:40
Always record calls to += and -= event accessors
... by matching and executing setups before event (un-) subscription
is dealt with.

This has the added benefit of making the interception pipeline more
efficient for almost all invocations.
because by the time we get to `HandleEventSubscription`, any setups
would already have executed.
Simplify event subscription and remove a remaining inconsistency
Enable parameterized `Mock.Of<>` in query comprehension `from` clause
Discord has several benefits over gitter and is now the de-facto place for other Microsoft large-scale projects (i.e. https://cs.dotnet.gg/)

I'm there now most of the time, so selfishly, I'd like to move this over too :)
When testing an argument's type against a "template type" that may in-
volve type matchers, we can no longer do a simple `IsAssignableFrom`;
we'd have to create our own type matcher-aware version of it that com-
pares each constituent part of both sides' types (visited in lockstep).

Implementing our own `IsAssignableFrom` is going to be quite difficult
because we also have to account for variance. Looking at both ECMA-335
and .NET Core's source code, that looks rather complex... let's not go
there.

What we'll do instead is much easier: we still visit both sides' types'
constituent parts, and whenever we encounter a type matcher in the
template type, we test it against the corresponding (sub-) type from
the argument. If it's a match, we substitute the argument type in the
template type. If all type matchers match, we end up with a rewritten
template type that no longer uses any matchers; we can then use `Is-
AssignableFrom` with _that_ template type and the argument type to let
the framework deal with variance.
The `Microsoft.Extensions.Logging.Abstractions.ILogger` scenario is one
that many people have shown interest in, so let's cover that one in
particular.
The SolutionDir property is only available when building from the
solution. For packing, it's actually not necessary and we could
also easily allow packing from the Moq.csproj project itself.

This change, in addition to enabling that, also makes the icon
path more flexible since it could be moved down to the src folder
(say) without requiring a change to the .csproj.
Since we're generating portable symbols, these are WAY smaller than
full PDBs used to be. Their size contribution to the package are
negligible and for convenience, it's more common nowadays to just
embed the symbols always in the assembly itself. In particular for
a library that is not deployed to production apps, this makes
perfect sense.

So stop generating the (effectively legacy) .snupkg.
kzu and others added 20 commits November 17, 2022 22:56
# devlooped/oss

- Don't fail on background workflows devlooped/oss@f08c3f2

# devlooped/.github
Add `setup.Verifiable(Times times, [string failMessage])` method
…1322)

* Project: Convert 'PackageLicenseUrl' to 'PackageLicenseExpression'
* Project: Add 'Copyright' as specified in license
…generic-non-void-method

Don't throw away generic type arguments in one `mock.Protected().Verify<T>()` method overload
@CLAassistant

CLAassistant commented May 10, 2023 •

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@kzu

kzu commented Aug 4, 2023

Copy link
Copy Markdown
Member

Thanks for the PR! Could you rebase on top of main, since files moved around?

@stakx stakx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I haven't reviewed & tested this in detail TBH, but it generally looks good to me.

The changelog should be first updated with the latest few missing releases though, otherwise the changelog entry will end up with an incorrect (already published) version.

Comment thread CHANGELOG.md
@kzu
kzu requested a review from Copilot May 26, 2025 11:54
@kzu kzu added the stale label May 26, 2025

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull Request Overview

This PR addresses issue #1341 by improving how ProtectedMock selects hidden virtual methods and adds tests to cover this scenario.

  • Adjusted GetMethod logic to prefer methods declared in derived classes when multiple matches exist.
  • Added a new test (SetupResultAllowsHiddenVirtualMethods) to verify setup of hidden protected methods.
  • Updated changelog to document the fix.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/Moq.Tests/ProtectedMockFixture.cs Added a test verifying setup of hidden protected methods.
src/Moq/Protected/ProtectedMock.cs Enhanced method resolution logic for hidden virtual methods.
CHANGELOG.md Documented the fix in the Unreleased fixed section.


for (Type type = typeof(T); type != typeof(object); type = type.BaseType)
{
var method = methods.SingleOrDefault(m => m.DeclaringType == typeof(T));

Copilot AI May 26, 2025

Copy link

Choose a reason for hiding this comment

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

The loop variable type is never used: the predicate always checks DeclaringType == typeof(T). It should use the loop variable type (m.DeclaringType == type) to correctly select the most derived declaring type.

Suggested change
var method = methods.SingleOrDefault(m => m.DeclaringType == typeof(T));
var method = methods.SingleOrDefault(m => m.DeclaringType == type);

Copilot uses AI. Check for mistakes.
Comment on lines +329 to +331
methods = methods.Where(m => m.GetParameterTypes().CompareTo(argTypes, exact, considerTypeMatchers: false));

if (methods.Count() < 2)

Copilot AI May 26, 2025

Copy link

Choose a reason for hiding this comment

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

Calling Count() on an IEnumerable will enumerate it; since it's used again below, consider materializing methods into a collection (e.g., var list = methods.ToList()) to avoid multiple enumerations.

Suggested change
methods = methods.Where(m => m.GetParameterTypes().CompareTo(argTypes, exact, considerTypeMatchers: false));
if (methods.Count() < 2)
methods = methods.Where(m => m.GetParameterTypes().CompareTo(argTypes, exact, considerTypeMatchers: false)).ToList();
if (methods.Count < 2)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.