ASP.NET Core 单元测试:如何Mock Url.Page()

2021-04-29 17:32:11 浏览数 (1)

点击上方蓝字关注“汪宇杰博客”

导语

在 ASP.NET Core 中,当你在 UrlHelperExtensions 类上使用扩展方法时,很难在单元测试中编写Mock。因为Moq框架不支持模拟扩展方法。

问题

例如,我的博客代码中使用了 Url.Page() 方法:

var callbackUrl = Url.Page("/Index", null, null, Request.Scheme);

但是单元测试中,像这样 Mock 就会爆:

var mockUrlHelper = new Mock<IUrlHelper>(MockBehavior.Strict);mockUrlHelper.Setup(x => x.Page("/Index", null, null, It.IsAny<string>())).Returns("callbackUrl").Verifiable();

爆炸现场

System.NotSupportedException : Unsupported expression: x => x.Page("/Index", null, null, It.IsAny<string>()) Extension methods (here: UrlHelperExtensions.Page) may not be used in setup / verification expressions.

解决方法

我们需要 Mock 这个拓展方法调用的底层方法。在本案例中,底层方法是

Microsoft.AspNetCore.Mvc.IUrlHelper.RouteUrl(UrlRouteContext routeContext)

我是怎么知道的呢?很简单,.NET 都已经开源多少年了,直接看一眼源代码就能知道微软如何单元测试 UrlHelperExtensions。

https://source.dot.net/

从微软的代码里复制两个助手方法

private Mock<IUrlHelper> CreateMockUrlHelper(ActionContext context = null)

{

context ??= GetActionContextForPage("/Page");

var urlHelper = _mockRepository.Create<IUrlHelper>();

urlHelper.SetupGet(h => h.ActionContext)

.Returns(context);

return urlHelper;

}

private static ActionContext GetActionContextForPage(string page)

{

return new()

{

ActionDescriptor = new()

{

RouteValues = new Dictionary<string, string>

{

{ "page", page },

}

},

RouteData = new()

{

Values =

{

[ "page" ] = page

}

}

};

}

修改我们的单元测试

var mockUrlHelper = CreateMockUrlHelper();mockUrlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>())).Returns("callbackUrl");

现在单元测试就能顺利跑过了!

完整的单元测试代码见下方供参考:

[Test]

public async Task SignOutAAD()

{

_mockOptions.Setup(m => m.Value).Returns(new AuthenticationSettings

{

Provider = AuthenticationProvider.AzureAD

});

var mockUrlHelper = CreateMockUrlHelper();

mockUrlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))

.Returns("callbackUrl");

var ctx = new DefaultHttpContext();

var ctl = CreateAuthController();

ctl.ControllerContext = new() { HttpContext = ctx };

ctl.Url = mockUrlHelper.Object;

var result = await ctl.SignOut();

Assert.IsInstanceOf(typeof(SignOutResult), result);

}

0 人点赞