You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键修正点说明

  1. 实体集名称匹配:将实体集名称改为与控制器一致的复数形式"Customers",避免OData路径解析失败。
  2. 手动应用查询选项:通过queryOptions.ApplyTo(rawResult)将URL中的过滤、分页条件应用到原始IQueryable结果上,模拟Web API管道中[EnableQuery]特性的处理逻辑。
  3. 路由数据配置:为请求添加正确的路由数据,确保OData查询上下文能识别目标实体集。
  4. URL路径修正:调整URL为/odata/Customers,匹配配置的OData路由前缀"odata"。

内容的提问来源于stack exchange,提问作者sandesh naik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:24:51