繁体   English   中英

Asp.net Core Identity 单元测试控制器操作

[英]Asp.net Core Identity unit test controller actions

我在确定要测试的方式和内容时遇到问题。

我有一个控制器,它注入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);
    }
}

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

    [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);

    }

其中 sut.RegisterNewUser(input) 中的“输入”; 指的是一个辅助类,它构建控制器操作所需的视图模型:

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
        };
    }
} 

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

  1. 创建有效的视图模型并返回成功消息
  2. 创建一个有效的视图模型,但返回一个 IdentityResponse 错误(例如用户存在),该错误被转换为
  3. 创建无效的视图模型并返回模型状态错误

使用返回 json 对象的抽象类处理错误。控制器的基类简单地构造了一个HttpResponseMessage以供返回。

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

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

希望这是足够的细节。

被测方法应该是异步的,而不是使用阻塞调用,即.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);
}

回顾 Happy path 场景和测试方法表明,无需设置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);        
}
  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);
}

而对于

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

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

[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);    
}

FluentAssertions被用来做所有的断言。 您可以同样轻松地使用Assert.* API。

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

如果您不想测试用户管理器,这里有一个使用 NUnit 的简单方法(您可以用 xUnit 做类似的事情)。 (我还展示了如何使用可用于设置模拟数据的内存数据库将 DbContext 传递给同一个控制器)

    private DbContextOptions<MyContextName> options;

    [OneTimeSetUp]
    public void SetUp()
    {
        options = new DbContextOptionsBuilder<MyContextName>()
            .UseInMemoryDatabase(databaseName: "MyDatabase")
            .Options;

        // Insert seed data into the in-memory mock database using one instance of the context
        using (var context = new MyContextName(options))
        {
            var testWibble = new Wibble { MyProperty = 1, MyOtherProperty = 2 ... };
            context.wibbles.Add(testWibble);

            context.SaveChanges();
        }
    }


    [Test]
    public void Some_TestMethod()
    {
        // Use a clean instance of the context to run the test
        using (var context = new MyDbContext(options))
        {
            var store = new UserStore<MyUserType>(context);
            var userManager = new UserManager<MyUserType>(store, null, null, null, null, null, null, null, null);

            MyController MyController = new MyController(userManager, context);

            ... test the controller
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM