【问题标题】:Mocking Url.RouteUrl模拟 Url.RouteUrl
【发布时间】:2026-02-14 09:30:02
【问题描述】:

我使用Asp.netCore,以下代码是我需要通过XUnit 测试的操作的一部分。问题是URL 在我测试操作方法时为空。我如何模拟URL 及其函数RoutUrl 以返回我期望的URL

var callbackUrl = Url.RouteUrl("ConfirmEmail", new { userId = user.Id, token }, Request.Scheme);

我也试过这段代码,但它根本不起作用。

string locationUrl = "http://location/";
var mockUrlHelper = new Mock<IUrlHelper>();
mockUrlHelper
    .Setup(x => x.RoutUrl("ConfirmEmail", It.IsAny<object>(), It.IsAny<string>()))
    .Returns(locationUrl);

_accountController.Url = mockUrlHelper.Object;

这是我正在测试的操作方法:

[HttpPost]
public async Task<JsonResult> SendEmailConfirmation(string email)
{
    if (string.IsNullOrEmpty(email)) throw new Exception("Inavlid parameter");

    var user = await _userManager.GetUserAsync(User);

    if (user.Email.ToLower() == email.ToLower().Trim())
        return Json(false);

    user.EmailConfirmed = false;
    user.Email = email;
    await _userManager.UpdateAsync(user);

    var token = await _userManager.GenerateChangeEmailTokenAsync(user, email);
    var callbackUrl = Url.RouteUrl("ConfirmEmail", new { userId = user.Id, token }, Request.Scheme);
    await _emailService.SendEmailConfirmationUserAsync(user.Email, user.FirstName, callbackUrl);

    return Json(true);
}

这是我的测试:

[Fact]
public async Task SendEmailConfirmation_NewEmail_ShouldReturnTrue()
{
    const string token = "TokenString";
    var applicationUser = StubFactory.GetUser();

    _userManagerMock
        .Setup(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>()))
        .ReturnsAsync(applicationUser);

    _userManagerMock
        .Setup(x => x.UpdateAsync(applicationUser))
        .ReturnsAsync(IdentityResult.Success);

    _userManagerMock
        .Setup(x => x.GenerateChangeEmailTokenAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
        .ReturnsAsync(token);

    _emailServiceMock
        .Setup(x => x.SendEmailConfirmationUserAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
        .ReturnsAsync(It.IsAny<EmailResult>());

    //ToDO  Mock Url.RoutUrl 

    string locationUrl = "http://location/";
    var mockUrlHelper = new Mock<IUrlHelper>();
    mockUrlHelper
        .Setup(x => x.RouteUrl("ConfirmEmail", It.IsAny<object>(), It.IsAny<string>()))
        .Returns(locationUrl);

    _accountController.Url = mockUrlHelper.Object;


    var result = await _accountController.SendEmailConfirmation("newemail@something.com");

    result.Value.ShouldBe(true);
    _userManagerMock.Verify(x => x.GetUserAsync(It.IsAny<ClaimsPrincipal>()), Times.Once);
    _userManagerMock.Verify(x => x.GenerateChangeEmailTokenAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Once);
    _emailServiceMock.Verify(x => x.SendEmailConfirmationUserAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}  

我在单元测试会话中收到的错误消息:

System.ArgumentNullException
Value cannot be null.
Parameter name: helper
at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.RouteUrl(IUrlHelper helper, 
String routeName, Object values, String protocol)

【问题讨论】:

    标签: c# unit-testing asp.net-core-mvc moq xunit


    【解决方案1】:

    您正在模拟的特定 RouteUrl 方法是一种扩展方法

    /// <summary>
    /// Generates a URL with an absolute path for the specified route <paramref name="routeName"/> and route
    /// <paramref name="values"/>, which contains the specified <paramref name="protocol"/> to use.
    /// </summary>
    /// <param name="helper">The <see cref="IUrlHelper"/>.</param>
    /// <param name="routeName">The name of the route that is used to generate URL.</param>
    /// <param name="values">An object that contains route values.</param>
    /// <param name="protocol">The protocol for the URL, such as "http" or "https".</param>
    /// <returns>The generated URL.</returns>
    public static string RouteUrl(
        this IUrlHelper helper,
        string routeName,
        object values,
        string protocol)
    {
        if (helper == null)
        {
            throw new ArgumentNullException(nameof(helper));
        }
    
        return helper.RouteUrl(routeName, values, protocol, host: null, fragment: null);
    }
    

    来源:UrlHelperExtensions.cs

    最终会归结为another extension method,从而创建UrlRouteContext

    由于 Moq 无法模拟扩展方法,因此您需要模拟该类以使扩展方法流程完成

    string locationUrl = "http://location/";
    var mockUrlHelper = new Mock<IUrlHelper>();
    mockUrlHelper
        .Setup(x => x.RouteUrl(It.IsAny<UrlRouteContext>()))
        .Returns(locationUrl);
    

    【讨论】: