编写用于在ASP.NET Web API中使用User.Identity.Name的方法的单元测试

42

我正在使用 ASP.NET Web API 的单元测试编写测试用例。

现在我有一个动作,该动作调用我在服务层中定义的某个方法,在那里我使用了以下代码行。

string username = User.Identity.Name;
// do something with username
// return something

现在我该如何为此创建单元测试方法,我遇到了空引用异常。我对编写单元测试和其他相关内容还比较新。

我想仅使用单元测试来解决这个问题。请帮我一下。


你是想测试Action方法还是Service层方法? - IMLiviu
9个回答

48

以下方法仅是其中一种:

public class FooController : ApiController {

    public string Get() {

        return User.Identity.Name;
    }
}

public class FooTest {

    [Fact]
    public void Foo() {

        var identity = new GenericIdentity("tugberk");
        Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
        var controller = new FooController();

        Assert.Equal(controller.Get(), identity.Name);
    }
}

3
这是一个 xUnit 的事情,不用担心它。 - tugberk
4
值得注意的是,这个解决方案不适用于MVC控制器,只适用于API控制器。 - JTech
只有 User.Identity.Name 完美运作,而 User.Identity.GetUserId() 方法却不行。 - OmGanesh
@tugberk 如何在MVC控制器中实现相同的功能? - Rajesh Shetty

17

这是在NerdDinner测试教程中我发现的另一种方法。它在我的情况下起作用:

DinnersController CreateDinnersControllerAs(string userName)
{

    var mock = new Mock<ControllerContext>();
    mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
    mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

    var controller = CreateDinnersController();
    controller.ControllerContext = mock.Object;

    return controller;
}

[TestMethod]
public void EditAction_Should_Return_EditView_When_ValidOwner()
{

    // Arrange
    var controller = CreateDinnersControllerAs("SomeUser");

    // Act
    var result = controller.Edit(1) as ViewResult;

    // Assert
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(DinnerFormViewModel));
}

请确保您阅读了完整的章节:模拟 User.Identity.Name 属性

该章节使用了 Moq 模拟框架,您可以通过 NuGet 在您的 测试项目 中安装它:http://nuget.org/packages/moq


1
是的,我最近开始在我的新项目中使用Moq,下次一定会考虑这种方法。 - Yasser Shaikh
1
我刚刚写了这个答案,因为被采纳的那个在我的情况下不起作用。不知道为什么... :) - Leniel Maccaferri
如果我没记错的话,这个答案更适用于MVC而不是Web API。可能这就是为什么被接受的答案对你不起作用的原因。 - Craig Brett

11

使用WebApi 5.0略有不同。现在你可以这样做:

controller.User = new ClaimsPrincipal(
  new GenericPrincipal(new GenericIdentity("user"), null));

16
我正在查看System.Web.Mvc.dll中v5.2.0.0版本的“Controller”,发现“IPrincipal User”只有“get”方法,没有“set”方法。 - pennstatephil

1
这是我的解决方案。
var claims = new List<Claim>
{
    new Claim(ClaimTypes.Name, "Nikita"),
    new Claim(ClaimTypes.NameIdentifier, "1")
};

var identity = new ClaimsIdentity(claims);
IPrincipal user = new ClaimsPrincipal(identity);

controller.User = user;

属性或索引器'ControllerBase.User'不能被赋值——它是只读的。 - Fred

1

0
如果您有很多控制器需要测试,我建议创建一个基类,在构造函数中创建一个GenericIdentityGenericPrincipal,并设置Thread.CurrentPrincipal
GenericPrincipal principal = new GenericPrincipal(new 
    GenericIdentity("UserName"),null); Thread.CurrentPrincipal = principal; 

然后继承那个类...这样每个单元测试类都会有原则对象集

[TestClass]
public class BaseUnitTest
{
    public BaseUnitTest()
    {
      GenericPrincipal principal = new GenericPrincipal(new GenericIdentity("UserName"),null);   
      Thread.CurrentPrincipal = principal;
    }
}


[TestClass]
public class AdminUnitTest : BaseUnitTest
{
   [TestMethod]
   public void Admin_Application_GetAppliction()
   {
   }
}

0

在这里,我找到了另一种方法来为控制器级别的测试从测试方法中设置用户身份名称的解决方案。

public static void SetUserIdentityName(string userId)
        {
            IPrincipal principal = null;
            principal = new GenericPrincipal(new GenericIdentity(userId), 
            new string[0]);
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }

0

0
不确定AspNetCore是否有很大的变化,但是现在在Net7中,你可以简单地这样做。
someController.ControllerContext.HttpContext = new DefaultHttpContext
{
    User = new GenericPrincipal(new GenericIdentity(userName) , new []{string.Empty})
};

一个带有工作单元测试的示例

[Test]
public async Task GetAll_WhenUserNotExist_ReturnsNotFound()
{
    // arrange
    var autoMock = AutoMock.GetLoose();

    const string userName = "harry@potter.com";

    var customersServiceMock = autoMock.Mock<ICustomerService>();
    customersServiceMock
        .Setup(service => service.GetAll(userName)).Throws<UserDoesNotExistException>();
    
    var customerController = autoMock.Create<CustomerController>();

    //
    // Create a new DefaultHttpContext and assign a generic identity which you can give a user name 
    customerController.ControllerContext.HttpContext = new DefaultHttpContext
    {
        User = new GenericPrincipal(new GenericIdentity(userName) , new []{string.Empty})
    };

    var httpResult = await customerController.GetAllCustomers() as NotFoundResult;
    
    // act
    Assert.IsNotNull(httpResult);
}

控制器的实现
    [HttpGet]
    [Route("all")]
    public async Task<IActionResult> GetAllCustomers()
    {
        // User identity name will return harry@potter now.
        var userName = User.Identity?.Name;
        try
        {
            var customers = await _customerService.GetAll(userName).ConfigureAwait(false);
            return Ok(customers);
        }
        catch (UserDoesNotExistException)
        {
            Log.Warn($"User {userName} does not exist");
            return NotFound();
        }
    }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接