devlooped/moq · error · ArgumentException

ex.Message from ReplaceDuck expression rewriting (rethrown…

Error message

ex.Message from ReplaceDuck expression rewriting (rethrown with paramName 'expression')

What it means

ProtectedAsMock.Setup rewrites the expression written against the 'analog' type TAnalog into an equivalent expression against the actual hidden type T via ReplaceDuck. When that rewriting encounters an invalid expression tree (e.g. a member that does not correspond to a protected member of T), it surfaces as ArgumentException with paramName 'expression', preserving the original message.

Solutions

  1. Ensure the TAnalog interface members exactly match the names, parameter types, and return types of the protected members on the mocked type
  2. Verify the target protected member exists and is virtual/overridable on the mocked class
  3. Read the inner message to identify which part of the expression failed the rewrite
  4. Change the expression to only reference members declared on TAnalog

Example fix

// before (analog member mismatches protected member)
public interface IAnalog { int Calc(int x); }
mock.Protected().As<IAnalog>().Setup(m => m.Calc(1));
// after (match the actual protected signature)
public interface IAnalog { int Calculate(int x); }
mock.Protected().As<IAnalog>().Setup(m => m.Calculate(1));
Defensive patterns

Strategy: try-catch

Validate before calling

// Before setup, confirm the protected member exists on the mocked type:
var ok = typeof(MyService)
    .GetMembers(BindingFlags.NonPublic | BindingFlags.Instance)
    .Any(m => m.Name == "Calculate" && m is MethodInfo mi && mi.IsVirtual);

Try / catch

try
{
    mock.Protected().As<IAnalog>().Setup(m => m.Calculate(1));
}
catch (ArgumentException ex) when (ex.ParamName == "expression")
{
    // Log ex.Message; fix the TAnalog member to match the protected member
}

Prevention

When it happens

Trigger: Calling mock.Protected().As<TAnalog>().Setup(x => x.SomeMember(...)) where SomeMember cannot be mapped to a protected member of the mocked type, or the expression uses constructs ReplaceDuck cannot rewrite.

Common situations: Analog interface method signatures that drift from the actual protected members (renamed/changed visibility); typos in the analog interface; non-virtual or non-existent protected members.

Understand the failure class

Background: "must be a positive integer", "cannot be empty", "invalid argument": how invalid-argument errors work across open-source libraries — this error's family across 33 libraries.

Related errors


AI-assisted analysis of devlooped/moq@89a5be629c (2026-09-16). Data as JSON: /api/errors/a274446ad20e568d. Report an issue: GitHub.

Appendix: source

Thrown at src/Moq/Protected/ProtectedAsMock.cs:42

        public ProtectedAsMock(Mock<T> mock)
        {
            Debug.Assert(mock != null);

            this.mock = mock;
        }

        public ISetup<T> Setup(Expression<Action<TAnalog>> expression)
        {
            Guard.NotNull(expression, nameof(expression));

            Expression<Action<T>> rewrittenExpression;
            try
            {
                rewrittenExpression = (Expression<Action<T>>)ReplaceDuck(expression);
            }
            catch (ArgumentException ex)
            {
                throw new ArgumentException(ex.Message, nameof(expression));
            }

            var setup = Mock.Setup(this.mock, rewrittenExpression, null);
            return new VoidSetupPhrase<T>(setup);
        }

        public ISetup<T, TResult> Setup<TResult>(Expression<Func<TAnalog, TResult>> expression)
        {
            Guard.NotNull(expression, nameof(expression));

            Expression<Func<T, TResult>> rewrittenExpression;
            try
            {
                rewrittenExpression = (Expression<Func<T, TResult>>)ReplaceDuck(expression);
            }
            catch (ArgumentException ex)
            {
                throw new ArgumentException(ex.Message, nameof(expression));

View on GitHub (pinned to 89a5be629c)