使用DI DbContext和使用LINQ - EF Core 2的模型对控制器进行单元测试

时间:2018-03-27 07:43:46

标签: asp.net unit-testing moq xunit

开始之前:

  1. 我们将PostgreSQL与Postgis扩展一起使用(不能使用inMemoryDB选项,因为我们正在使用Postgis'几何体)
  2. 我们不使用存储库模式
  3. 我们使用DbContext访问DbModel并在其上使用Linq表达式
  4. 我们使用数据库优先原则
  5. 之前已经在不同程度Like here, where they talk about it, however we do NOT implement the repository pattern 提出了这类问题,但我觉得它没有以简洁的方式回答,所以我再次提出这个问题。 欢迎参考或一般指南,所以提前谢谢。

    这是我到目前为止所拥有的。 控制器:

    public class StartTripController : Controller
    {
        private readonly DbContext _dbContext;
    
        public StartTripController(DbContext DbContext) => _DbContext = DbContext;
    
        [Route("connect")]
        [HttpGet]
        public async Task<IActionResult> StartTrip(MessageDto messageDto, StartTripDto startTripDto)
        {
    
            if (ModelState.ErrorCount > 0)
                return StatusCode(400);
    
            var userToCheck = await _DbContext.User
                                                .Select(i => new UserDto { UserId = i.Id, PhoneId = i.PhoneId, AppInfoDto = new AppInfoDto { IsAppInDebug = false } })
                                                .SingleOrDefaultAsync(u => u.PhoneId == startTripDto.UserDto.PhoneId);   //checks if User is in DB, returns Null if not
    
            if (userToCheck == null) //user does not exist
            {
              //Make new User entity and save the changes to DB async
    
                UserDto newUserToReturn = new UserDto { UserId = user.Id, AppInfoDto = new AppInfoDto { IsAppInDebug = user.DebugMode } };
    
                return GenerateResponseWithStatus200(messageDto, newUserToReturn);
            }
    
            //user exists
            return GenerateResponseWithStatus200(messageDto, userToCheck);
        }
    

    我的测试看起来像这样:

     public class StartTripControllerTest : ControllerTest<StartTripController>
    {
        private DbContext _mockDbContext;
    
        protected override StartTripController GetController()
        {
    
            var mockDbContext = new Mock<DbContext>();
    
            var userData = new List<User>
            {
                new User{PhoneId = "Phone1", Id = 1, ReportProviderId = 1, UserPhone = null, DebugMode = true, IpAddress = "empty", DeviceUser = null, Credential = null},
                new User{PhoneId = "Phone2", Id = 2, ReportProviderId = 2, UserPhone = null, DebugMode = true, IpAddress = "empty", DeviceUser = null, Credential = null}
            };
    
            var mockData = userData.AsQueryable().BuildMock(); //BuildMock is from https://github.com/romantitov/MockQueryable
            mockDbContext.Setup(x => ???what do I write here??).Returns(mockData.Object);
    
            return new StartTripController(mockDbContext.Object);
        }
    
    
    
        [Fact]
        public async System.Threading.Tasks.Task StartTrip_ReturnUser_JsonAsync()
        {
            // Arrange
            StartTripController startTripController = GetController();
    
            MessageDto messageDto = new MessageDto ();
            StartTripDto startTripDto = new StartTripDto();
             //code omitted for readiblity
            var result = await startTripController.StartTrip(messageDto, startTripDto);
        }
    }
    

    我想出的事情:

    1. 您必须使用模拟接口才能使异步方法正常工作As per Microsoft's post
    2. 我坚持的事情:

      1. 如何在这种情况下模拟DbContext或我猜模型,以便我可以使用LINQ表达式,就像我常规代码一样?

1 个答案:

答案 0 :(得分:1)

所以我想分享一下我对这个问题的回答。 @Fabio建议的主要是我在原始问题中发布的第一个参考主题。

我将我的数据库交互移动到&#34; UserManager&#34;如果您愿意,可以提供服务现在我的控制器看起来像这样:

  public class StartTripController : Controller
{

    private readonly IUserManager _userManager;

    public StartTripController( IUserManager userManager)
    {
        _userManager = userManager;
    }

    [Route("connect")]
    [HttpGet]
    public async Task<IActionResult> StartTrip(MessageDto messageDto, StartTripDto startTripDto)
    {
        messageDto.Message = Any.Pack(startTripDto);

        if (ModelState.ErrorCount > 0)
            return StatusCode(400);

        var userToCheck = await _userManager.FindUser(startTripDto.UserDto);

        if (userToCheck == null) //user does not exist
        {
            var newUser = await _userManager.AddUser(startTripDto.UserDto);
            return GenerateResponseWithStatus200(messageDto, newUser);
        }

        //user exists
        await _userManager.StartTripExistingUser(userToCheck); 
        return GenerateResponseWithStatus200(messageDto, userToCheck);
    }
}

这自动改变了我的测试,因为我没有模拟数据库,这大大简化了我的问题。

我的测试看起来像这样:  public class StartTripControllerTest:ControllerTest     {

    protected override StartTripController GetController()
    {

        var mockUserManager = new Mock<IUserManager>();

        AppInfoDto appInfoDto = new AppInfoDto {IsAppInDebug = true};
        UserDto userDto = new UserDto {UserId = 1818, PhoneId = "Phone1", AppInfoDto = appInfoDto};

        mockUserManager.Setup(p => p.FindUser(It.IsAny<UserDto>())).Returns(Task.FromResult(userDto));
        return new StartTripController(mockUserManager.Object);
    }

    [Fact]
    [Trait("Unit", "Controller")]
    public void StartTrip_ReturnUser_BadRequestAsync()
    {
        // Arrange
        StartTripController startTripController = GetController();
        MessageDto messageDto = new MessageDto { ApiVersion = "1.3" };
        AppInfoDto appInfoDto = new AppInfoDto { IsAppInDebug = true };
        UserDto userDto = new UserDto { PhoneId = "Phone1", AppInfoDto = appInfoDto };
        StartTripDto startTripDto = new StartTripDto { UserDto = userDto };

        startTripController.ModelState.AddModelError(ModelBinderError.MissingUserId.errorKey, ModelBinderError.MissingUserId.errorValue);
        var result = startTripController.StartTrip(messageDto, startTripDto).Result as StatusCodeResult;
        Assert.True(result.StatusCode == 400);
    }
}

}

上面的例子显示了Controller初始化&#34; GetController&#34;方法,还显示了如何使用ModelState。

然而,这并没有解决潜在的问题,只是将其移动到系统的不同部分。当您必须测试UserManager时,您仍然遇到模拟数据库的问题。

为了测试系统的这一部分,您需要进行交互测试。 对于SQLServer,您可以使用InMemoryDatabase,但是因为我使用的是Postgresql,所以我需要使用TestDatabase。

总结一下我的UserManager测试看起来像这样:

public class UserManagerIntegrationTests
{
 private readonly TestServer _server;
 private readonly HttpClient _client;

 public UserManagerIntegrationTests()
 {
  // Arrange
    _server = new TestServer(new WebHostBuilder()
                         .UseStartup<StartupWithTestDatabase>());//Startup file contains the TestDatabase connection string
   _client = _server.CreateClient();
 }
 // ... 
}

P.S。我现在正在查看的是Fluent Assertions,它基本上用改变的方法替换了正常的Assert.True()。

PPS仅供将来参考,我在ASP.NET Core 2的单元测试中找到的最新教程是this one,它还展示了一个示例,您可以在其中与控制器和与之配合的服务进行集成测试你的数据库。