Asp.net核心识别单元测试控制器动作

BWG*_*BWG 9 c# unit-testing moq asp.net-core asp.net-core-identity

我在研究如何测试以及测试什么方面遇到了问题.

我有一个控制器注入UserManager并调用该CreateAsync方法来创建一个新用户.

我不想测试Identity用户管理器,因为它已经经过了彻底的测试.我想做的是测试控制器是否运行正确的路径(在我的情况下,有3条路径,发回响应模型状态错误,身份响应错误或简单字符串)

我是否应该尝试创建用户管理器的模拟以创建我的测试(我不确定如何将用户管理器设置为模拟依赖项)其次,如何设置条件以验证控制器是否采用了给定的路径.

我正在使用xUnitMoq.

[Route("api/[controller]")]
public class MembershipController : BaseApiController
{
    private UserManager<ApplicationUser> _userManager;

    public MembershipController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpGet("RegisterNewUser")]
    public HttpResponseMessage RegisterNewUser([FromBody] NewUserRegistration user)
    {
        if (ModelState.IsValid)
        {
            ApplicationUser newUser = new ApplicationUser();
            newUser.UserName = user.username;
            newUser.Email = user.password;
            IdentityResult result = _userManager.CreateAsync(newUser, user.password).Result;

            if (result.Errors.Count() > 0)
            {
                var errors = new IdentityResultErrorResponse().returnResponseErrors(result.Errors);
                return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
            }
        }
        else
        {
            var errors = new ViewModelResultErrorResponse().returnResponseErrors(ModelState);
            return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
        }

        return this.WebApiResponse(
                    "We have sent a valifation email to you, please click on the verify email account link.",
                    HttpStatusCode.OK);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的单元测试中,我有以下内容来测试一个快乐的路径场景

    [Fact]
    public void RegisterNewUser_ReturnsHttpStatusOK_WhenValidModelPosted()
    {
        var mockStore = new Mock<IUserStore<ApplicationUser>>();
        var mockUserManager = new Mock<UserManager<ApplicationUser>>(mockStore.Object, null, null, null, null, null, null, null, null);

        ApplicationUser testUser = new ApplicationUser { UserName = "user@test.com" };

        mockStore.Setup(x => x.CreateAsync(testUser, It.IsAny<CancellationToken>()))
           .Returns(Task.FromResult(IdentityResult.Success));

        mockStore.Setup(x => x.FindByNameAsync(testUser.UserName, It.IsAny<CancellationToken>()))
                    .Returns(Task.FromResult(testUser));


        mockUserManager.Setup(x => x.CreateAsync(testUser).Result).Returns(new IdentityResult());

        MembershipController sut = new MembershipController(mockUserManager.Object);
        var input = new NewUserInputBuilder().Build();
        sut.RegisterNewUser(input);

    }
Run Code Online (Sandbox Code Playgroud)

在sut.RegisterNewUser(输入)中的"输入"; 指的是一个辅助类,它构造了控制器动作所需的视图模型:

public class NewUserInputBuilder
{
    private string username { get; set; }
    private string password { get; set; }
    private string passwordConfirmation { get; set; }
    private string firstname { get; set; }
    private string lastname { get; set; }

    internal NewUserInputBuilder()
    {
        this.username = "user@test.com";
        this.password = "password";
        this.passwordConfirmation = "password";
        this.firstname = "user";
        this.lastname = "name";
    }

    internal NewUserInputBuilder WithNoUsername()
    {
        this.username = "";
        return this;
    }

    internal NewUserInputBuilder WithMisMatchedPasswordConfirmation()
    {
        this.passwordConfirmation = "MismatchedPassword";
        return this;
    }

    internal NewUserRegistration Build()
    {
        return new NewUserRegistration
        { username = this.username, password = this.password,
            passwordConfirmation = this.passwordConfirmation,
            firstname = this.firstname, lastname = this.lastname
        };
    }
} 
Run Code Online (Sandbox Code Playgroud)

我的目标是通过测试强制3个条件:

  1. 创建有效的viewmodel并返回成功消息
  2. 创建有效的viewmodel但返回转换为的IdentityResponse错误(例如,user exists)
  3. 创建一个无效的viewmodel并返回Modelstate错误

使用返回json对象的抽象类来处理错误HttpResponseMessage.控制器的基类只是构造一个for返回.

基本上我想通过强制测试降低模型状态错误路径,identityresult.errors路径以及可以实现快乐路径来检查是否调用了正确的错误响应类.

然后我的计划是单独测试错误响应类.

希望这是足够的细节.

Nko*_*osi 2

被测试的方法应该是异步的并且不使用阻塞调用,即.Result

[HttpGet("RegisterNewUser")]
public async Task<HttpResponseMessage> RegisterNewUser([FromBody] NewUserRegistration user) {
    if (ModelState.IsValid) {
        var newUser = new ApplicationUser() {
            UserName = user.username,
            Email = user.password
        };
        var result = await _userManager.CreateAsync(newUser, user.password);
        if (result.Errors.Count() > 0) {
            var errors = new IdentityResultErrorResponse().returnResponseErrors(result.Errors);
            return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
        }
    } else {
        var errors = new ViewModelResultErrorResponse().returnResponseErrors(ModelState);
        return this.WebApiResponse(errors, HttpStatusCode.BadRequest);
    }

    return this.WebApiResponse(
                "We have sent a valifation email to you, please click on the verify email account link.",
                HttpStatusCode.OK);
}
Run Code Online (Sandbox Code Playgroud)

对快乐路径场景和测试方法的审查表明,无需设置,因为测试UserStore将直接覆盖用户管理器虚拟成员。

请注意,测试也已设为异步。

  1. 创建有效的视图模型并返回成功消息
[Fact]
public async Task RegisterNewUser_ReturnsHttpStatusOK_WhenValidModelPosted() {
    //Arrange
    var mockStore = Mock.Of<IUserStore<ApplicationUser>>();
    var mockUserManager = new Mock<UserManager<ApplicationUser>>(mockStore, null, null, null, null, null, null, null, null);

    mockUserManager
        .Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
        .ReturnsAsync(IdentityResult.Success);

    var sut = new MembershipController(mockUserManager.Object);
    var input = new NewUserInputBuilder().Build();

    //Act
    var actual = await sut.RegisterNewUser(input);

    //Assert
    actual
        .Should().NotBeNull()
        .And.Match<HttpResponseMessage>(_ => _.IsSuccessStatusCode == true);        
}
Run Code Online (Sandbox Code Playgroud)
  1. 创建一个有效的视图模型,但返回一个 IdentityResponse 错误(例如,用户存在)并进行转换

为此,您只需设置模拟以返回有错误的结果。

[Fact]
public async Task RegisterNewUser_ReturnsHttpStatusBadRequest_WhenViewModelPosted() {
    //Arrange

    //...code removed for brevity

    mockUserManager
        .Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()))
        .ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "test"}));

    //...code removed for brevity

    //Assert
    actual
        .Should().NotBeNull()
        .And.Match<HttpResponseMessage>(_ => _.StatusCode == HttpStatusCode.BadRequest);
}
Run Code Online (Sandbox Code Playgroud)

而对于

  1. 创建无效的视图模型并返回 Modelstate 错误

您只需设置控制器的模型状态使其无效即可。

[Fact]
public async Task RegisterNewUser_ReturnsHttpStatusBadRequest_WhenInvalidModelState() {
    //Arrange
    var mockStore = Mock.Of<IUserStore<ApplicationUser>>();
    var mockUserManager = new Mock<UserManager<ApplicationUser>>(mockStore, null, null, null, null, null, null, null, null);

    var sut = new MembershipController(mockUserManager.Object);
    sut.ModelState.AddModelError("", "invalid data");
    var input = new NewUserInputBuilder().Build();

    //Act
    var actual = await sut.RegisterNewUser(input);

    //Assert
    actual
        .Should().NotBeNull()
        .And.Match<HttpResponseMessage>(_ => _.StatusCode == HttpStatusCode.BadRequest);    
}
Run Code Online (Sandbox Code Playgroud)

FluentAssertions用于执行所有断言。您可以轻松使用Assert.*API。

这应该足以让您解决上述问题。