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

时间:2017-11-17 10:12:53

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

我在研究测试方法和测试方法时遇到了问题。

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

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

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

我正在使用 xUnit Moq

[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)中的“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. 创建有效的viewmodel并返回成功消息
  2. 创建有效的viewmodel但返回一个IdentityResponse错误(例如,用户存在),该错误将转换为
  3. 创建无效的viewmodel并返回Modelstate errors
  4. 使用返回json对象的抽象类处理错误 控制器的基类只构造一个HttpResponseMessage来返回。

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

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

    希望这足够详细。

2 个答案:

答案 0 :(得分:2)

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

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

注意,测试也是异步的。

  
      
  1. 创建有效的viewmodel并返回成功消息
  2.   
[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. 创建有效的viewmodel但返回转换后的IdentityResponse错误(例如用户存在)
  2.   

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

[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. 创建无效的viewmodel并返回Modelstate errors
  2.   

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

[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。

这应该足以让你顺利解决上述问题。

答案 1 :(得分:0)

如果不想测试用户管理器,这是使用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
        }
    }