devlooped/moq · error · ArgumentException

Expression is not a property access

Error message

Expression is not a property access: {0}

What it means

ToPropertyInfo extracts property metadata from an expression used in a property setup. It requires the lambda body to be a MemberExpression referring to a property access; anything else throws ArgumentException 'Expression is not a property access: {0}'. This keeps property setup APIs (mock.SetupProperty / Stub-style APIs) restricted to real properties.

Solutions

  1. Pass a direct property access on the lambda parameter: `x => x.PropertyName`.
  2. If you intended method setup, use `mock.Setup(x => x.Method())` instead of the property API.
  3. Ensure the member is a public instance property, not a field — convert the field to a property if needed.
  4. Verify the lambda body compiles to MemberExpression with NodeType MemberAccess; simplify multi-level chains to the property directly owned by the mock type.

Example fix

// before
mock.SetupProperty(x => x.Compute()); // method, not property
// after
mock.SetupProperty(x => x.Value);
Defensive patterns

Strategy: type-guard

Validate before calling

static bool IsPropertyAccess<T, R>(Expression<Func<T, R>> e) =>
    e.Body is MemberExpression { Member: PropertyInfo };

Type guard

static bool IsDirectProperty<T, R>(Expression<Func<T, R>> e) =>
    e.Body is MemberExpression { Member: PropertyInfo p, Expression: ParameterExpression }
    && p.CanRead;

Try / catch

try
{
    mock.SetupProperty(expr);
}
catch (ArgumentException ex) when (ex.Message.StartsWith("Expression is not a property access"))
{
    // switch to mock.Setup(...) for methods or fix the lambda
}

Prevention

When it happens

Trigger: Passing a non-property lambda to an API that calls ToPropertyInfo: `x => x.Method()`, `x => x.Field + 1`, `x => x.Prop.Value` when Prop.Value resolves oddly, or an indexer access `x => x[0]`.

Common situations: Setting up fields instead of properties; accidentally passing method getters; writing `x => x.SomeProperty.SubProperty` where the target API expects a single-level property on the mock's type; dynamic/expanded expressions whose body is not MemberExpression.

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/a2c6992cadfac57b. Report an issue: GitHub.

Appendix: source

Thrown at src/Moq/ExpressionExtensions.cs:405

                        (derivedProperty.CanWrite(out var setter) && setter.GetBaseDefinition() == property.GetSetMethod(true)))
                        return derivedProperty;
                }
            }

            return property;
        }

        /// <summary>
        /// Converts the body of the lambda expression into the <see cref="PropertyInfo"/> referenced by it.
        /// </summary>
        public static PropertyInfo ToPropertyInfo(this LambdaExpression expression)
        {
            if (expression.Body is MemberExpression prop)
            {
                return prop.GetReboundProperty();
            }

            throw new ArgumentException(string.Format(
                CultureInfo.CurrentCulture,
                Resources.SetupNotProperty,
                expression.ToStringFixed()));
        }

        /// <summary>
        /// Checks whether the body of the lambda expression is a property access.
        /// </summary>
        public static bool IsProperty(this LambdaExpression expression)
        {
            Debug.Assert(expression != null);

            return expression.Body is MemberExpression memberExpression && memberExpression.Member is PropertyInfo;
        }

        /// <summary>
        ///   Checks whether the body of the lambda expression is a indexer access.
        /// </summary>

View on GitHub (pinned to 89a5be629c)