尝试为 ASP.Net Core 3.1 单元测试创​​建 Mock.Of<ControllerContext>() 时出错

Error trying to create Mock.Of<ControllerContext>() for ASP.Net Core 3.1 Unit Test

根据 Moq 快速入门的最后一部分定义 here,我正在尝试配置以下 Mock 以便将表单值传递给被测控制器方法:

var formCollection = new FormCollection(
                new System.Collections.Generic.Dictionary<string, Microsoft.Extensions.Primitives.StringValues>()
            {
                    {"mAction", "someAction" },
                    {"mRefId", "0" }
            });

        var controllerContext = Mock.Of<ControllerContext>(ctx =>
            ctx.HttpContext.Request.Form == formCollection);
        
        controller.ControllerContext = controllerContext;

但是,当 运行 测试时,它在 Mock.Of<> 行失败并出现以下错误:

System.NotSupportedException : Unsupported expression: mock => mock.HttpContext

Non-overridable members (here: ActionContext.get_HttpContext) may not be used in setup / verification expressions.

我错过了什么?我不是按照快速入门文档中定义的示例做的吗?

错误是因为ControllerContext.HttpContext属性不是virtual,所以Moq无法覆盖它。

考虑使用实际的 ControllerContext 并模拟 HttpContext 以分配给 属性

var formCollection = new FormCollection(new Dictionary<string, StringValues>()
    {
        {"mAction", "someAction" },
        {"mRefId", "0" }
    });

var controllerContext = new ControllerContext() {
    HttpContext = Mock.Of<HttpContext>(ctx => ctx.Request.Form == formCollection)
};

controller.ControllerContext = controllerContext;

//...

甚至使用 DefaultHttpContext 并分配所需的值

var formCollection = new FormCollection(new Dictionary<string, StringValues>()
    {
        {"mAction", "someAction" },
        {"mRefId", "0" }
    });

HttpContext httpContext = new DefaultHttpContext();
httpContext.Request.Form = formCollection;

var controllerContext = new ControllerContext() {
    HttpContext = httpContext
};

controller.ControllerContext = controllerContext;

//...

您需要手动设置所有未标记 virtual 的属性,方法是在 lambda 中显式设置它们,或者可能在外部设置它们(无需创建具体对象),这是通过我至少在版本 4.16.1.

方法一(使用Mock.Of()时):

var mockCtx = Mock.Of<ControllerContext>(ctx =>
        ctx.HttpContext == Mock.Of<HttpContext>(hCtx => hCtx.Request.Form 
== formCollection))`

注意:如果您需要设置要模拟的所有属性,或做任何需要 Mock.Get() 的事情,如 this answer 您需要直接在 HttpContext 上做在 Mock.Get(mockCtx.HttpContext ) 而不是 mockCtx.

方法二(使用new Mock<>()时):

var mockCtx = new Mock<ControllerContext>();
mockCtx.Object.HttpContext = Mock.Of<HttpContext>(hCtx => hCtx.Request.Form
                                                         == formCollection);

无需创建具体对象,因为方法 2 只是使用模拟对象做同样的事情。

注意:这仅在 属性 不是只读的情况下才有效。