.NET OData Web API单元测试问题:URL过滤条件未生效
OData Web API单元测试过滤失效问题排查与解决
问题描述
在为.NET OData Web API编写单元测试时,发现HTTP请求URL中传入的过滤条件未生效,返回的始终是原始模拟数据,无法实现预期的过滤效果。
相关代码
客户控制器代码
public class CustomersController : ODataController { private readonly ICustomerRepository _repository; public CustomersController() { _repository = new CustomerRepository(new ODataSampleEntities()); } public CustomersController(ICustomerRepository repository) { _repository = repository; } [EnableQuery] public IQueryable<Customer> Get() { return _repository.GetAllCustomers(); } }
Customer模型代码
public partial class Customer { public int Id { get; set; } public string Name { get; set; } public string Country { get; set; } public string Address { get; set; } }
仓储层代码
public interface ICustomerRepository { IQueryable<Customer> GetAllCustomers(); } public class CustomerRepository: ICustomerRepository { private readonly ODataSampleEntities _context; public CustomerRepository(ODataSampleEntities context) { _context = context; } public virtual IQueryable<Customer> GetAllCustomers() { return _context.Customers; } }
原单元测试代码
public class CustomerControllerTests { [TestMethod] public void GetAllCustomers_ShouldReturnAllCustomers() { // Arrange var customers = new List<Customer> { new Customer { Id = 1, Name = "John", Country = "USA", Address = "USA" }, new Customer { Id = 2, Name = "Sandesh", Country = "India", Address = "Goa India" } }; var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44395/Customers?$filter=Name eq 'Sandesh' & $top=1"); HttpRouteCollection routes = new HttpRouteCollection(); HttpConfiguration config = new HttpConfiguration(routes) { IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always }; var Customerbuilder = new ODataConventionModelBuilder(); Customerbuilder.EntitySet<Customer>("Customer"); var model = Customerbuilder.GetEdmModel(); // attempting to register at least some non-OData HTTP route doesn't seem to help routes.MapHttpRoute("Default", "{controller}/{action}"); config.MapODataServiceRoute("odata", "odata", model); config.Count().Filter().OrderBy().Expand().Select().MaxTop(null); config.EnableDependencyInjection(); config.EnsureInitialized(); request.SetConfiguration(config); ODataQueryContext context = new ODataQueryContext( model, typeof(Customer), new ODataPath( new Microsoft.OData.UriParser.EntitySetSegment( model.EntityContainer.FindEntitySet("Customer")) ) ); var mockRepository = new Mock<ICustomerRepository>(); mockRepository.Setup(r => r.GetAllCustomers()).Returns(customers.AsQueryable()); mockRepository.SetReturnsDefault<IEnumerable<Customer>>(customers.AsQueryable()); // Create the controller var controller = new CustomersController(mockRepository.Object); controller.Request = request; var options = new ODataQueryOptions<Customer>(context, request); //var response = controller.Get(); //as ViewResult; // Act var result = controller.Get(); // Assert Assert.IsNotNull(result); Assert.IsInstanceOfType(result, typeof(IEnumerable<Customer>)); var contentResult = result; Assert.IsNotNull(contentResult); Assert.AreEqual(1, contentResult.Count()); Assert.AreEqual("Sandesh", contentResult.First().Name); } }
问题原因
直接调用controller.Get()方法时,[EnableQuery]特性不会自动生效——该特性是Web API请求处理管道的一部分,只有当请求经过完整的Web API路由和筛选流程时才会触发。单元测试中直接调用控制器方法相当于跳过了这层处理,且代码中创建了ODataQueryOptions却未实际使用,导致URL中的过滤条件无法应用到结果上。
此外,原代码中实体集名称("Customer")与控制器名称("Customers")不匹配,可能导致OData路径解析错误,进一步影响过滤逻辑。
修正后的单元测试代码
public class CustomerControllerTests { [TestMethod] public void GetCustomers_WithFilter_ShouldReturnFilteredResult() { // Arrange var customers = new List<Customer> { new Customer { Id = 1, Name = "John", Country = "USA", Address = "USA" }, new Customer { Id = 2, Name = "Sandesh", Country = "India", Address = "Goa India" } }; // 匹配OData路由前缀的完整URL路径 var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44395/odata/Customers?$filter=Name eq 'Sandesh'&$top=1"); var config = new HttpConfiguration(); var modelBuilder = new ODataConventionModelBuilder(); // 实体集名称与控制器名称保持一致(复数形式) modelBuilder.EntitySet<Customer>("Customers"); var edmModel = modelBuilder.GetEdmModel(); // 配置OData路由 config.MapODataServiceRoute("odata", "odata", edmModel); config.Count().Filter().OrderBy().Expand().Select().MaxTop(null); config.EnsureInitialized(); // 设置正确的路由数据,确保OQuery上下文识别请求的实体集 request.SetConfiguration(config); request.SetRouteData(new HttpRouteData(config.Routes["odata"], new HttpRouteValueDictionary { { "controller", "Customers" } })); // 创建OData查询上下文 var queryContext = new ODataQueryContext( edmModel, typeof(Customer), ODataPathTemplate.Parse("Customers", edmModel) ); // 模拟仓储 var mockRepository = new Mock<ICustomerRepository>(); mockRepository.Setup(r => r.GetAllCustomers()).Returns(customers.AsQueryable()); // 初始化控制器 var controller = new CustomersController(mockRepository.Object); controller.Request = request; controller.Configuration = config; // 创建查询选项 var queryOptions = new ODataQueryOptions<Customer>(queryContext, request); // Act var rawResult = controller.Get(); // 手动应用OData查询条件,模拟[EnableQuery]特性的处理逻辑 var filteredResult = queryOptions.ApplyTo(rawResult) as IQueryable<Customer>; var resultList = filteredResult.ToList(); // Assert Assert.IsNotNull(resultList); Assert.AreEqual(1, resultList.Count); Assert.AreEqual("Sandesh", resultList.First().Name); } }
关键修正点说明
- 实体集名称匹配:将实体集名称改为与控制器一致的复数形式"Customers",避免OData路径解析失败。
- 手动应用查询选项:通过
queryOptions.ApplyTo(rawResult)将URL中的过滤、分页条件应用到原始IQueryable结果上,模拟Web API管道中[EnableQuery]特性的处理逻辑。 - 路由数据配置:为请求添加正确的路由数据,确保OData查询上下文能识别目标实体集。
- URL路径修正:调整URL为
/odata/Customers,匹配配置的OData路由前缀"odata"。
内容的提问来源于stack exchange,提问作者sandesh naik
相关产品推荐
相关产品推荐

