devlooped/moq · error · ArgumentException

ex.Message (re-thrown ArgumentException with paramName…

Error message

ex.Message (re-thrown ArgumentException with paramName 'expression')

What it means

Same rethrow pattern as Verify: ProtectedAsMock.VerifyGet rewrites the lambda from TAnalog to the mocked type T, and any ArgumentException during that rewriting (typically a missing matching protected property) is rethrown with paramName 'expression'. The message describes which protected member could not be matched.

Solutions

  1. Add or fix the corresponding protected property on the mocked type T so the duck-typing rewrite can match it
  2. Align TAnalog's property declaration with the actual protected property name/type on T
  3. Use Protected().SetupGet/VerifyGet with the exact string member name on the non-As API if the member is hard to express in the analog
  4. Use the strong-typed VerifyGet on Mock<T> if the property is public

Example fix

// before (analog property name does not exist as protected on T)
mock.Protected().As<IAnalog>().VerifyGet(a => a.Value);
// after
// class T { protected virtual string Value { get {...} } }
mock.Protected().As<IAnalog>().VerifyGet(a => a.Value);
Defensive patterns

Strategy: validation

Validate before calling

static bool HasMatchingProtectedProperty(Type target, string name) =>
    target.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Any(p => p.Name == name);

Try / catch

try { mock.Protected().As<IAnalog>().VerifyGet(a => a.Value); }
catch (ArgumentException ex) when (ex.ParamName == "expression") { /* fix analog/target property mismatch */ }

Prevention

When it happens

Trigger: Calling mock.Protected().As<TAnalog>().VerifyGet(x => x.SomeProperty, ...) where TAnalog's property has no corresponding protected property on T.

Common situations: Analog type declares a property that is a field or method on T, property renamed/made public on T, or the analog interface was hand-written against an older version of the mocked class.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        {
            Guard.NotNull(setterExpression, nameof(setterExpression));

            var rewrittenExpression = ReconstructAndReplaceSetter(setterExpression);
            Mock.VerifySet(mock, rewrittenExpression, times.HasValue ? times.Value : Times.AtLeastOnce(), failMessage);
        }

        public void VerifyGet<TProperty>(Expression<Func<TAnalog, TProperty>> expression, Times? times = null, string? failMessage = null)
        {
            Guard.NotNull(expression, nameof(expression));

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

            Mock.VerifyGet(this.mock, rewrittenExpression, times ?? Times.AtLeastOnce(), failMessage);
        }

        LambdaExpression ReconstructAndReplaceSetter(Action<TAnalog> setterExpression)
        {
            var expression = ExpressionReconstructor.Instance.ReconstructExpression(setterExpression, mock.ConstructorArguments);
            return ReplaceDuck(expression);
        }

        static LambdaExpression ReplaceDuck(LambdaExpression expression)
        {
            Debug.Assert(expression.Parameters.Count == 1);

            var targetParameter = Expression.Parameter(typeof(T), expression.Parameters[0].Name);
            return Expression.Lambda(DuckReplacerInstance.Visit(expression.Body), targetParameter);
        }

View on GitHub (pinned to 89a5be629c)