【问题标题】:Unit Testing Controller with Authorization ASP.NET Core 2.0 Web API具有授权 ASP.NET Core 2.0 Web API 的单元测试控制器
【发布时间】:2018-09-29 05:22:44
【问题描述】:

我有一个控制器:

public class InvitationsController: Controller {
        private readonly IMapper _mapper;
        private readonly IInvitationManager _invitationManager;
        private readonly UserManager<MyAppUser> _userManager;

        public InvitationsController(
            IInvitationManager invitationManager,
            IMapper mapper,
            UserManager<MyAppUser> userManager,
            IJobManager jobManager
        ) {
            _invitationManager = invitationManager;
            _mapper = mapper;
            _userManager = userManager;
        } 
[Authorization]
GetInvitationByCode(string code) { ... }

我正在尝试使用 Xunit 和 Moq 编写单元测试。这是我的测试的实现:

  public class InvitationsControllerTests {

    private Mock<IInvitationManager> invitationManagerMock;        
    private Mock<UserManager<MyAppUser>> userManagerMock;
    private Mock<IMapper> mapperMock;
    private InvitationsController controller;

    public InvitationsControllerTests() {
        invitationManagerMock = new Mock<IInvitationManager>();          
        userManagerMock = new Mock<UserManager<MyAppUser>>();

        mapperMock = new Mock<IMapper>();
        controller = new InvitationsController(invitationManagerMock.Object,
                   mapperMock.Object,
                   userManagerMock.Object);
    }

    [Fact]
    public async Task GetInvitationByCode_ReturnsInvitation() {

        var mockInvitation = new Invitation {
            StoreId = 1,
            InviteCode = "123abc",
        };

        invitationManagerMock.Setup(repo => 
        repo.GetInvitationByCodeAsync("123abc"))
            .Returns(Task.FromResult(mockInvitation));

        var result = await controller.GetInvitationByCode("123abc");

        Assert.Equal(mockInvitation, result);
    }

我认为我没有正确使用模拟功能。特别是与 UserManager。我找不到关于使用 Moq 测试受 [Authorize] 保护的控制器的明确答案。运行我的测试时,它会引发异常

        controller = new InvitationsController(invitationManagerMock.Object,
                   mapperMock.Object,
                   userManagerMock.Object);

内容如下:

Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: '无法实例化类的代理:Microsoft.AspNetCore.Identity.UserManager`1[[MyApp.api.Core.Models.MyAppUser, MyApp.api, Version=1.0.0.0, Culture=中性,PublicKeyToken=null]]。 找不到无参数构造函数。'

【问题讨论】:

  • UserManager 是一个应该被抽象出来的实现问题。将使模拟和注入更容易。
  • 在测试控制器类时,您将无法测试AuthorizationAttribute 的实际工作。因为AuthorizationAttribute 被中间件消耗。对于您需要测试完整管道的测试:创建客户端(HttpClient),启动您的 WebAPI,向您正在测试的 url 发送请求并断言响应。

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


【解决方案1】:

您不是在进行单元测试;你是集成测试。当你发现自己设置一万个模拟来运行一个方法时,这是一个很好的迹象,它是一个集成测试。此外,诸如授权之类的事情仅作为请求生命周期的一部分发生;如果不执行实际请求,则无法对其进行测试,这再次意味着您正在进行集成测试。

因此,请使用test host

private readonly TestServer _server;
private readonly HttpClient _client;

public MyTestClass()
{
    _server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>());
    _client = _server.CreateClient();
}

[Fact]
public async Task GetInvitationByCode_ReturnsInvitation() {

    var mockInvitation = new Invitation {
        StoreId = 1,
        InviteCode = "123abc",
    };

    var response = await _client.GetAsync("/route");
    response.EnsureSuccessStatusCode();

    var responseString = await response.Content.ReadAsStringAsync();
    var result = JsonConvert.DeserializeObject<Invitation>(responseString);

    // Compare individual properties you care about.
    // Comparing the full objects will fail because of reference inequality
    Assert.Equal(mockInvitation.StoreId, result.StoreId);
}

如果您需要构建数据以返回正确的结果,只需使用in-memory database provider。将其用于集成测试的最简单方法是指定一个新环境,如“测试”。然后,在您的启动中,当配置您的上下文时,在环境中进行分支并在环境为“测试”时使用内存提供程序(而不是 SQL Server 或其他)。然后,在为集成测试设置测试服务器时,只需在 .UseStartup&lt;Startup&gt;() 之前添加 .UseEnvironment("Test")

【讨论】:

  • 在使用控制器的方法测试控制器时,您会推荐集成测试方法而不是单元测试吗?
  • 控制器动作,是的。如果您的控制器上有某种实用方法不是一个动作,您可以对其进行单元测试。动作本质上是集成。除非您通过请求点击它,否则您不会知道它是否真的有效。
  • 这个答案确实让我走上了正轨,但是您如何“伪造”经过身份验证的用户,以便您可以运行显示授权通过的测试?即你如何测试应用的授权属性?即 [Authorize(Policy = "Is18orOlder")] 或任何可能的策略?
  • 将一个或多个测试用户插入内存数据库。然后,使用测试客户端点击您的登录发布操作并发送用户名并为您要测试的用户传递。响应将包含您的身份验证 cookie。使用它再次通过测试客户端发出经过身份验证的请求。根据被测试的用户,相应地断言授权通过或失败。
  • 我已经完成并看到了一些复杂的东西,因为我不知道这一点,与我一起工作的任何人也不知道。这将是我的集成测试向前迈出的一大步。谢谢!
【解决方案2】:

我认为,问题在于依赖注入。在您的 Startups.cs 文件中,您可以找到类似的字符串:services.AddIdentity&lt;AppUser, AppRole&gt;().AddEntityFrameworkStores&lt;AppDbContext&gt;().AddDefaultTokenProviders(); 这意味着命名空间Microsoft.Extensions.DependencyInjection 的魔力为您提供了一个用户或角色管理器的实例,您可以在任何您想使用它的地方使用它。例如,在 InvitationsController 中使用构造函数中的注入。

您可以尝试在测试类中注入 UserManger 并模拟它。或阅读类似的question

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 2022-11-25
    • 1970-01-01
    • 2023-01-19
    相关资源
    最近更新 更多