【问题标题】:Unit Testing an ASP.Net Web API Controller that relies on the User.Identity object对依赖于 User.Identity 对象的 ASP.Net Web API 控制器进行单元测试
【发布时间】:2018-04-04 13:55:28
【问题描述】:

我首先要说我对单元测试相当陌生,但我现在是一个坚定的信徒。我以前的工作总是有一个测试员,所以我从来不用担心这个。

我有一个正在替换 Web 服务的 ASP.Net MVC API。 API 从 Web 应用程序获取 HTTP Post,将请求记录到该数据库,然后发出请求并从另一个数据库返回数据。它正在替换的 Web 服务具有额外的开销,以确保随请求提交的用户名是登录用户。为了替换它,我使用了站点身份验证和关联控制器的 User.Identity 对象。

现在我需要为此编写一个单元测试。我可以使用 Moq 为 MVC 控制器模拟经过身份验证的用户,但似乎找不到如何为 API 模拟经过身份验证的用户。这是我想要做的:

... // In the test method
        DbApiController controller = new DbApiController ();
        controller.ControllerContext = GetMockContext();
...

    private HttpControllerContext GetMockContext()
    {
        var identity = new GenericIdentity("testuser");
        var controllerContext = new Mock<HttpControllerContext>();
        var principal = new Mock<IPrincipal>();
        principal.Setup(p => p.IsInRole("SalesAndEstimating"));
        principal.SetupGet(p => p.Identity.Name).Returns("testuser");
        controllerContext.SetupGet(c => c.RequestContext.Principal).Returns(principal.Object);

        return controllerContext.Object;
    }

我遇到了异常:

System.NotSupportedException: Invalid setup on a non-virtual (overridable in VB) member: mock => mock.RequestContext

我在这里做错了什么?

更新代码:

        ApiController controller = new DbApiController();

        var context = new Mock<HttpContextBase>();

        var identity = new GenericIdentity("testuser");
        identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "1"));
        var principal = new GenericPrincipal(identity, new[] { "user" });
        context.Setup(s => s.User).Returns(principal);

        controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);

        var results = ((DbApiController)controller).Post(...);

        Assert.AreEqual(results.Success, true);

进行上述更改后,我仍然收到错误“参数 3:无法从 'System.Web.Http.ApiController' 转换为 'System.Web.Mvc.ControllerBase'”。

【问题讨论】:

    标签: c# unit-testing asp.net-web-api


    【解决方案1】:

    你不能设置RequestContext,因为它不是虚拟的,所以它不能被 Mock 覆盖。

    你可以使用这个代码

    var context = new Mock<HttpContextBase>(); 
    
    var identity = new GenericIdentity("testuser");
    identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "1"));
    var principal = new GenericPrincipal(identity, new[] { "user" } );
    context.Setup(s => s.User).Returns(principal);
    
    ApiController controller = new ApiController();
    controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
    

    【讨论】:

    • 谢谢,我想我现在的困惑是根据您发布的代码从哪里获取变量“context”。
    • 谢谢。我需要更多的咖啡。我应该知道的。
    • 现在我得到参数 3:无法从 'DbApiController' 转换为 'System.Web.Mvc.ControllerBase'
    • 你的控制器是用于 WebApi 还是 MVC?
    • 它是一个WebApi控制器。
    【解决方案2】:

    我经常看到这种情况,在单元测试方面似乎存在很大的误解,以及应该或不应该测试什么以及如何测试。

    您对功能、业务规则以及以某种方式更改数据的任何内容进行单元测试。您不会对控制器进行单元测试。

    理想情况下,您希望您的控制器非常简单,接受一个请求,调用一些服务来执行任何需要完成的操作并返回结果。控制器真的应该很轻。如果您像这样编写代码,那么您不需要对它们进行单元测试,您可以对实际执行某些操作的服务进行单元测试。

    现在,您仍然希望测试并确保您的 API 正常工作,因此您的下一步是为您的端点编写集成测试,这就是 Postman 之类的工具的用武之地。该工具还具有可以与 CI 服务器集成的优点,这使得集成测试非常容易添加到您的管道中。

    所以简而言之,想想你在单元测试,单元测试功能,不要试图测试你正在使用的框架,这是完成的,应该由编写框架的人完成。

    当然会使用模拟,但是如果您模拟来自数据库的请求和模拟响应,这并不意味着您的代码可以工作,即使您的测试通过了,数据库也可能会以不同的方式工作并抛出错误t 真的让你的代码变得更好。

    单元测试应该是小型的、集中的,它们不涉及任何外部系统,例如磁盘上的文件、数据库、其他 API。这样做的原因是您希望它们运行得非常快,即使您有数千个,它们也需要在几秒钟或更短的时间内运行,这样您就可以一直运行它们。作为开发人员,您应该一直运行它们以确保您没有破坏任何东西。

    不要陷入为了增加任意数量而添加测试的陷阱。

    【讨论】:

    • 我在结构化、非 OOP 语言方面的多年经验正在展示。因此,我需要遵循的最佳实践是将所有业务逻辑移出控制器并放入我可以独立于控制器进行测试的服务/库中。控制器应该做的就是调用库函数。
    【解决方案3】:

    受 Kahbazi 实施的启发,有一条更简单的方法。不过,这是 AspNetCore。

    // set up identity and required claims
    var identity = new GenericIdentity("username");
    identity.AddClaim(new Claim("<your-type>", "<your-value>"));
    
    // set up claims principal
    var principal = new ClaimsPrincipal(identity);
    
    // set up httpcontext
    var context = new DefaultHttpContext()
    {
        User = principal
    };
    
    // associate httpcontext to controller
    controller.ControllerContext = new ControllerContext
    {
        HttpContext = context
    };
    

    【讨论】:

      猜你喜欢
      • 2017-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-26
      • 1970-01-01
      • 2014-01-11
      • 2019-04-26
      相关资源
      最近更新 更多