【问题标题】:How to mock all layer of application in test method?如何在测试方法中模拟所有应用层?
【发布时间】:2020-02-17 09:39:45
【问题描述】:

我正在尝试使用 XUnit 项目对我的 ASP.NET Core WebAPI 执行单元测试。我正在使用 3 层架构,其中每一层都在不同的类库项目中,并且依赖项通过依赖注入来解决。一切正常。但是当我想执行单元测试时,我陷入了对逻辑层和数据层的模拟。当我运行测试时,我得到了这个错误:

消息:System.ArgumentException:无法为接口模拟传递构造函数参数。

我的模拟对象在这个类中生成:

public static class MockObjects
{
    public static  Mock<UserManager<TUser>> GetUserManagerMock<TUser>() where TUser : IdentityUser
    {
        return new Mock<UserManager<TUser>>(
            new Mock<IUserStore<TUser>>().Object,
            new Mock<IOptions<IdentityOptions>>().Object,
            new Mock<IPasswordHasher<TUser>>().Object,
            new IUserValidator<TUser>[0],
            new IPasswordValidator<TUser>[0],
            new Mock<ILookupNormalizer>().Object,
            new Mock<IdentityErrorDescriber>().Object,
            new Mock<IServiceProvider>().Object,
            new Mock<ILogger<UserManager<TUser>>>().Object);
    }

    public static Mock<SignInManager<TUser>> GetSignInManagerMock<TUser>() where TUser : IdentityUser
    {
        return new Mock<SignInManager<TUser>>(
            GetUserManagerMock<TUser>().Object,
            new Mock<IHttpContextAccessor>().Object,
            new Mock<IUserClaimsPrincipalFactory<TUser>>().Object,
            new Mock<IOptions<IdentityOptions>>().Object,
            new Mock<ILogger<SignInManager<TUser>>>().Object,
            new Mock<IAuthenticationSchemeProvider>().Object
            );
    }

    public static Mock<RoleManager<TIdentityRole>> GetRoleManagerMock<TIdentityRole>() where TIdentityRole : IdentityRole
    {
        return new Mock<RoleManager<TIdentityRole>>(
            new Mock<IRoleStore<TIdentityRole>>().Object,
            new IRoleValidator<TIdentityRole>[0],
            new Mock<ILookupNormalizer>().Object,
            new Mock<IdentityErrorDescriber>().Object,
            new Mock<ILogger<RoleManager<TIdentityRole>>>().Object);
    }

    public static Mock<ApplicationDbContext> GetDbContextMock()
    {
        return new Mock<ApplicationDbContext>(new DbContextOptions<ApplicationDbContext>());
    }

    public static Mock<TRepository> GetRepositoryMock<TRepository>() where TRepository : class
    {
        return new Mock<TRepository>(GetDbContextMock().Object);
    }

    public static Mock<ISmsService> GetSmsServiceMock()
    {
        return new Mock<ISmsService>(GetRepositoryMock<SmsRepository>().Object);
    }

    public static Mock<IEmailService> GetEmailServiceMock()
    {
        return new Mock<IEmailService>(GetRepositoryMock<EmailRepository>().Object);
    }

    public static Mock<IProfileService> GetProfileServiceMock()
    {
        return new Mock<IProfileService>(GetRepositoryMock<ProfileRepository>().Object);
    }

    public static Mock<IAccountService> GetAccountServiceMock()
    {
        return new Mock<IAccountService>(
            GetUserManagerMock<ApplicationUser>().Object,
            GetSignInManagerMock<ApplicationUser>().Object,
            GetSmsServiceMock().Object,
            GetEmailServiceMock().Object,
            new Mock<IConfiguration>().Object,
            GetProfileServiceMock().Object
            );
    }
}

我在这篇博文之后实现了这个类: https://dejanstojanovic.net/aspnet/2019/september/unit-testing-repositories-in-aspnet-core-with-xunit-and-moq/

我的测试方法:

[Fact]
public async Task RegisterSimpleTest()
{
    // Arrange
    var mockService = MockObjects.GetAccountServiceMock().Object;
    var controller = new AccountController(mockService);
    var data = new EmailRegisterViewModel()
    {
        Email = "a@a.a",
        Password = "123456",
        ConfirmPassword = "123456"
    };
    // Act
    var result = await controller.Register(data) as ObjectResult;

    // Assert
    Assert.NotNull(result);
    Assert.Equal(200, result.StatusCode);
}

我要测试的控制器动作:

[HttpPost]
public async Task<IActionResult> Register([FromBody]EmailRegisterViewModel model)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelStateExtention.GetModelErrors(ModelState);
        return BadRequest(errors);
    }
    var user = new ApplicationUser()
    {
        UserName = model.Email,
        Email = model.Email
    };

    var result = await _accountService.EmailRegisterAsync(user, model.Password);
    return StatusResultExtension<ActionMessage>.GetStatusResult(result);
}

如何执行单元测试?我做错了什么?

【问题讨论】:

  • 控制器动作应该经过集成测试。为您的帐户服务等内容预留单元测试。

标签: c# unit-testing asp.net-core dependency-injection xunit.net


【解决方案1】:

您正在尝试将参数传递给 mock,但这是行不通的。

如果您想在单元测试中将IAccountService 的mock 传递给您的Controller,您可以执行以下操作:

[Fact]
    public async Task RegisterSimpleTest()
    {
        // Arrange
        var mockService = new Mock<IAccountService>();
        mockService.Setup(mock => mock.EmailRegisterAsync( // all you need to mock goes here)

        var controller = new AccountController(mockService.Object);
        var data = new EmailRegisterViewModel()
        {
            Email = "a@a.a",
            Password = "123456",
            ConfirmPassword = "123456"
        };
        // Act
        var result = await controller.Register(data) as ObjectResult;

        // Assert
        Assert.NotNull(result);
        Assert.Equal(200, result.StatusCode);
    }

请咨询quickstart,了解如何mock 的行为。

另外,我建议反对在一个班级中设置您的mocks 并在任何地方使用它。您需要灵活地更改mocks 每个测试的行为。如果您发现设置很麻烦,您可以查看AutoFixture.AutoMoq,它可以让您只设置在特定测试中指定行为所需的依赖项。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-28
    • 1970-01-01
    • 2016-07-20
    相关资源
    最近更新 更多