如何测试依赖Service与Repository分层架构的API?
Repository-Service架构的单元测试方案疑问
我有四个采用Repository-Service模式的项目,各层职责如下:
- 数据访问层:负责从数据库获取数据;
- 仓储层:通过数据访问层对数据库实体执行CRUD操作,通用接口示例:
public interface IEntityBaseRepository<T> where T : class, IEntityBase, new() { IEnumerable<T> AllIncluding( params Expression<Func<T, object>>[] includeProperties ); IEnumerable<T> GetAll(); int Count(); T GetSingle(int id); T GetSingle(Expression<Func<T, bool>> predicate); T GetSingle( Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties ); IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate); T Add(T entity); void Update(T entity); void Delete(T entity); void DeleteWhere(Expression<Func<T, bool>> predicate); void Commit(); }
- 服务层:处理应用业务逻辑,依赖仓储层,示例代码:
public class EmployeeService : IEmployeeService { private IEntityBaseRepository<Employee> _employeeRepository; private IEntityBaseRepository<User> _userRepository; private IEntityBaseRepository<UserRole> _userRoleRepository; private IEntityBaseRepository<Role> _roleRepository; public EmployeeService(IEntityBaseRepository<Employee> employeeRepository, IEntityBaseRepository<User> userRepository, IEntityBaseRepository<UserRole> userRoleRepository, IEntityBaseRepository<Role> roleRepository) { _employeeRepository = employeeRepository; _userRepository = userRepository; _userRoleRepository = userRoleRepository; _roleRepository = roleRepository; } // ... 其他方法省略 public List<EmployeeServiceModel> GetAllEmployeesForUser(string username) { var user = _userRepository.GetAll().FirstOrDefault(u => u.UserName == username); var userRoles = _userRoleRepository.GetAll().Where(ur => ur.UserId == user.Id); var roles = _roleRepository.GetAll(); var currentRoles = new List<Role>(); if (userRoles != null && userRoles.Count() > 0) { currentRoles = roles.Where(r => userRoles.Any(ur => ur.RoleId == r.Id)).ToList(); } else { throw new UnauthorizedAccessException(); } // ... 后续业务逻辑省略 }
- API层:对外暴露服务,控制器示例:
public class EmployeesController : ControllerBase { private IEmployeeService _employeeService; public EmployeesController(IEmployeeService employeeService) { _employeeService = employeeService; } // GET: api/<EmployeesController> [HttpGet] public async Task<IActionResult> Get(string username) { var employees = _employeeService.GetAllEmployeesForUser(username); return Ok(employees); } }
我的疑问:
- 该如何对这套架构进行单元测试?
- 是否只需测试API层?如果是,该如何实现?
- 项目依赖Service接口,而Service又依赖Repository,我无法直接伪造员工列表让Service的
GetAllEmployeesForUser方法返回。 - 另外,如何测试用户未授权、无员工等场景?
- 是否只需测试服务层,无需测试API层?
单元测试方案解答
1. 分层测试,而非只测某一层
这套架构的核心是职责分离,单元测试需要对应分层验证:
- 仓储层:如果只是简单封装数据访问(比如EF Core的DbSet操作),无需单独写单元测试,用集成测试验证它和数据库的交互即可;如果有自定义复杂查询逻辑,再针对这些逻辑写单元测试,此时可以伪造数据访问层的依赖(比如Mock DbContext)。
- 服务层:这是单元测试的核心。服务层承载了业务逻辑,必须单独测试,因为这部分是业务规则的核心体现。
- API层:需要测试,但属于控制器测试,重点验证请求路由、参数绑定、返回状态码和响应格式,而非业务逻辑。
2. 服务层单元测试:通过Mock仓储接口实现伪造数据
你可以用Moq、NSubstitute等Mock框架,伪造IEntityBaseRepository<T>的各个实现,控制仓储层的返回结果,进而测试服务层的业务逻辑。
举个测试GetAllEmployeesForUser的例子(用Moq):
[TestClass] public class EmployeeServiceTests { private Mock<IEntityBaseRepository<User>> _mockUserRepo; private Mock<IEntityBaseRepository<UserRole>> _mockUserRoleRepo; private Mock<IEntityBaseRepository<Role>> _mockRoleRepo; private Mock<IEntityBaseRepository<Employee>> _mockEmployeeRepo; private EmployeeService _service; [TestInitialize] public void Setup() { // 初始化所有Mock对象 _mockUserRepo = new Mock<IEntityBaseRepository<User>>(); _mockUserRoleRepo = new Mock<IEntityBaseRepository<UserRole>>(); _mockRoleRepo = new Mock<IEntityBaseRepository<Role>>(); _mockEmployeeRepo = new Mock<IEntityBaseRepository<Employee>>(); // 实例化服务,注入Mock依赖 _service = new EmployeeService( _mockEmployeeRepo.Object, _mockUserRepo.Object, _mockUserRoleRepo.Object, _mockRoleRepo.Object ); } // 测试用户未授权场景:用户无角色时抛出异常 [TestMethod] [ExpectedException(typeof(UnauthorizedAccessException))] public void GetAllEmployeesForUser_UserHasNoRoles_ThrowsUnauthorized() { // 1. 准备测试数据:模拟存在用户,但无对应角色关联 var testUser = new User { Id = 1, UserName = "testUser" }; _mockUserRepo.Setup(repo => repo.GetAll()).Returns(new List<User> { testUser }); _mockUserRoleRepo.Setup(repo => repo.GetAll()).Returns(new List<UserRole>()); // 2. 调用测试方法 _service.GetAllEmployeesForUser("testUser"); } // 测试正常授权且有员工的场景 [TestMethod] public void GetAllEmployeesForUser_ValidUserWithRoles_ReturnsEmployees() { // 1. 准备测试数据 var testUser = new User { Id = 1, UserName = "testUser" }; var testRole = new Role { Id = 100, Name = "Admin" }; var testUserRole = new UserRole { UserId = 1, RoleId = 100 }; var testEmployees = new List<Employee> { new Employee { Id = 200, Name = "John" } }; // 配置Mock返回 _mockUserRepo.Setup(repo => repo.GetAll()).Returns(new List<User> { testUser }); _mockUserRoleRepo.Setup(repo => repo.GetAll()).Returns(new List<UserRole> { testUserRole }); _mockRoleRepo.Setup(repo => repo.GetAll()).Returns(new List<Role> { testRole }); _mockEmployeeRepo.Setup(repo => repo.GetAll()).Returns(testEmployees); // 2. 调用方法 var result = _service.GetAllEmployeesForUser("testUser"); // 3. 验证结果 Assert.IsNotNull(result); Assert.AreEqual(1, result.Count); // 可以进一步验证EmployeeServiceModel的属性是否正确映射 } // 测试无员工的场景 [TestMethod] public void GetAllEmployeesForUser_ValidUserButNoEmployees_ReturnsEmptyList() { // 1. 准备数据:用户有角色,但无员工 var testUser = new User { Id = 1, UserName = "testUser" }; var testRole = new Role { Id = 100, Name = "Admin" }; var testUserRole = new UserRole { UserId = 1, RoleId = 100 }; _mockUserRepo.Setup(repo => repo.GetAll()).Returns(new List<User> { testUser }); _mockUserRoleRepo.Setup(repo => repo.GetAll()).Returns(new List<UserRole> { testUserRole }); _mockRoleRepo.Setup(repo => repo.GetAll()).Returns(new List<Role> { testRole }); _mockEmployeeRepo.Setup(repo => repo.GetAll()).Returns(new List<Employee>()); // 2. 调用方法 var result = _service.GetAllEmployeesForUser("testUser"); // 3. 验证 Assert.IsNotNull(result); Assert.AreEqual(0, result.Count); } }
3. API层测试:Mock服务接口,验证控制器行为
API层的测试不需要关心业务逻辑,只需要验证:
- 控制器是否正确调用服务方法
- 参数是否正确传递
- 返回的状态码、响应格式是否符合预期
同样用Moq实现示例:
[TestClass] public class EmployeesControllerTests { private Mock<IEmployeeService> _mockEmployeeService; private EmployeesController _controller; [TestInitialize] public void Setup() { _mockEmployeeService = new Mock<IEmployeeService>(); _controller = new EmployeesController(_mockEmployeeService.Object); } [TestMethod] public async Task Get_ValidUsername_ReturnsOkWithEmployees() { // 1. 准备测试数据 var testEmployees = new List<EmployeeServiceModel> { new EmployeeServiceModel { Name = "John" } }; _mockEmployeeService.Setup(svc => svc.GetAllEmployeesForUser("testUser")).Returns(testEmployees); // 2. 调用控制器方法 var result = await _controller.Get("testUser"); // 3. 验证结果 var okResult = result as OkObjectResult; Assert.IsNotNull(okResult); Assert.AreEqual(200, okResult.StatusCode); var returnedEmployees = okResult.Value as List<EmployeeServiceModel>; Assert.AreEqual(1, returnedEmployees.Count); } [TestMethod] public async Task Get_UserUnauthorized_ReturnsUnauthorized() { // 1. 模拟服务抛出未授权异常 _mockEmployeeService.Setup(svc => svc.GetAllEmployeesForUser("unauthorizedUser")) .Throws(new UnauthorizedAccessException()); // 2. 调用方法 var result = await _controller.Get("unauthorizedUser"); // 3. 验证返回401状态码 var unauthorizedResult = result as UnauthorizedResult; Assert.IsNotNull(unauthorizedResult); Assert.AreEqual(401, unauthorizedResult.StatusCode); } }
4. 总结
- 必须测试服务层:这是业务逻辑的核心,通过Mock仓储层隔离外部依赖,覆盖所有业务场景(授权、无数据、正常数据等)。
- 建议测试API层:验证控制器的请求处理逻辑,确保接口行为符合预期(状态码、响应格式等)。
- 仓储层可选测试:如果是简单封装数据访问,用集成测试验证;如果有复杂逻辑,再写单元测试。
内容的提问来源于stack exchange,提问作者William
相关产品推荐
相关产品推荐

