我该如何对EntitySetController进行单元测试?

3
我正在尝试对一个EntitySetController进行单元测试。我能够测试Get方法,但在测试Post方法时遇到了问题。
我已经尝试过SetODataPath和SetODataRouteName,但当我调用this.sut.Post(entity)时,会出现许多关于缺少Location Header、缺少OData-Path、缺少Routes的错误。
我已经陷入了困境。是否有人成功地测试过他们的EntitySetController呢?
有没有人能为我提供一些想法?也许我应该仅测试我的EntitySetController实现中受保护的重写方法?但是如何测试受保护的方法呢?
感谢您的帮助。
5个回答

5

我也在寻找解决方案。这种方法似乎有效,但不确定是否有更好的方法。

控制器需要至少覆盖CreateEntityGetKey

public class MyController : EntitySetController<MyEntity, int>
{
    protected override MyEntity CreateEntity(MyEntity entity)
    {
        return entity;
    }

    protected override int GetKey(MyEntity entity)
    {
        return entity.Id;
    }
}

MyEntity非常简单:

public class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

看起来您至少需要: + 带有URI的请求 + 请求头中的3个关键字,MS_HttpConfiguration, MS_ODataPathMS_ODataRouteName + 一个带有路由的HTTP配置

[TestMethod]
    public void CanPostToODataController()
    {
        var controller = new MyController();

        var config = new HttpConfiguration();
        var request = new HttpRequestMessage();

        config.Routes.Add("mynameisbob", new MockRoute());

        request.RequestUri = new Uri("http://www.thisisannoying.com/MyEntity");
        request.Properties.Add("MS_HttpConfiguration", config);
        request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
        request.Properties.Add("MS_ODataRouteName", "mynameisbob");

        controller.Request = request;

        var response = controller.Post(new MyEntity());

        Assert.IsNotNull(response);
        Assert.IsTrue(response.IsSuccessStatusCode);
        Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
    }

我对IHttpRoute不太确定,在aspnet源代码中(为了弄清楚这一切,我不得不链接到此处),测试使用该接口的模拟。因此,对于这个测试,我只需创建一个该接口的模拟,并实现RouteTemplate属性和GetVirtualPath方法。在测试期间,没有使用接口上的其他方法。

public class MockRoute : IHttpRoute
{
    public string RouteTemplate
    {
        get { return ""; }
    }

    public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
    {
        return new HttpVirtualPathData(this, "www.thisisannoying.com");
    }

    // implement the other methods but they are not needed for the test above      
}

这对我来说是有效的,但我真的不太确定关于 ODataPathIHttpRoute 如何正确设置它。


谢谢你的回答。对我来说看起来不错。同时,我使用受保护方法调用单元测试我的 OData 控制器,并相信 Web.API 团队的正确实现和我的集成测试。 - Andreas Richter

1

除了来自@mynameisbob的答案外,我发现您可能还需要在请求属性中设置HttpRequestContext:

var requestContext = new HttpRequestContext();
requestContext.Configuration = config;
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);

例如,当我创建一个HttpResponseMessage时,需要上述添加内容:

public virtual HttpResponseException NotFound(HttpRequestMessage request)
    {
        return new HttpResponseException(
            request.CreateResponse(
                HttpStatusCode.NotFound,
                new ODataError
                {
                    Message = "The entity was not found.",
                    MessageLanguage = "en-US",
                    ErrorCode = "Entity Not Found."
                }
            )
        );
    }

如果没有设置HttpRequestContext,上述方法将抛出参数为空异常,因为CreateResponse扩展方法试图从HttpRequestContext获取HttpConfiguration(而不是直接从HttpRequest获取)。

1

好的,已更新答案。

我还发现要成功执行返回的IHttpActionResult,需要更多的东西。

到目前为止,这是我找到的最佳方法,我相信有更好的方法,但这对我有效:

// Register OData configuration with HTTP Configuration object
// Create an ODataConfig or similar class in App_Start 
ODataConfig.Register(config);

// Get OData Parameters - suggest exposing a public GetEdmModel in ODataConfig
IEdmModel model = ODataConfig.GetEdmModel();
IEdmEntitySet edmEntitySet = model.EntityContainers().Single().FindEntitySet("Orders"); 
ODataPath path = new ODataPath(new EntitySetPathSegment(edmEntitySet));

// OData Routing Convention Configuration
var routingConventions = ODataRoutingConventions.CreateDefault();

// Attach HTTP configuration to HttpRequestContext
requestContext.Configuration = config;

// Attach Request URI
request.RequestUri = requestUri;

// Attach Request Properties
request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", new DefaultODataPathHandler());

0
除了上述所有内容之外,我不得不手动将上下文附加到请求中,并创建路由数据。不幸的是,我没有找到一种无需依赖路由/模型配置进行单元测试的方法。
因此,使用名为“ODataRoute”的路由,它是在我的静态ODataConfig.Configure()方法中建立的常规配置的一部分(与上面相同,它创建了模型并调用了一堆MapODataServiceRoute),以下代码可准备控制器进行测试:
protected static void SetupControllerForTests(ODataController controller, 
    string entitySetName, HttpMethod httpMethod)
{
    //perform "normal" server configuration
    var config = new HttpConfiguration();
    ODataConfig.Configure(config);

    //set up the request
    var request = new HttpRequestMessage(httpMethod, 
        new Uri(string.Format("http://localhost/odata/{0}", entitySetName)));

    //attach it to the controller
    //note that this will also automagically attach a context to the request!
    controller.Request = request;

    //get the "ODataRoute" route from the configuration
    var route = (ODataRoute)config.Routes["ODataRoute"];

    //extract the model from the route and create a path
    var model = route.PathRouteConstraint.EdmModel;
    var edmEntitySet = model.FindDeclaredEntitySet(entitySetName);
    var path = new ODataPath(new EntitySetPathSegment(edmEntitySet));

    //get a couple more important bits to set in the request
    var routingConventions = route.PathRouteConstraint.RoutingConventions;
    var pathHandler = route.Handler;

    //set the properties of the request
    request.SetConfiguration(config);
    request.Properties.Add("MS_ODataPath", path);
    request.Properties.Add("MS_ODataRouteName", "ODataRoute");
    request.Properties.Add("MS_EdmModel", model);
    request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
    request.Properties.Add("MS_ODataPathHandler", pathHandler);

    //set the configuration in the request context
    var requestContext = (HttpRequestContext)request.Properties[HttpPropertyKeys.RequestContextKey];
    requestContext.Configuration = config;

    //get default route data based on the generated URL and add it to the request
    var routeData = route.GetRouteData("/", request);
    request.SetRouteData(routeData);
}

这让我花费了几天时间才拼凑出来,所以我希望至少能帮助其他人节省同样的时间。


0
此外,为了获取正确的位置标头值等,您确实希望调用Web Api应用程序OData配置代码。
因此,与其使用:
config.Routes.Add("mynameisbob", new MockRoute());

你应该将设置OData路由的WebApiConfig类部分分离到一个单独的类中(例如ODataConfig),并使用它来为你的测试注册正确的路由:
例如:
ODataConfig.Register(config);

你需要注意的是以下行是否与你的路由配置匹配:

request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");

如果您的Web API OData配置如下:

    config.Routes.MapODataRoute("ODataRoute", "odata", GetEdmModel());

    private static IEdmModel GetEdmModel()
        {
            ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
            modelBuilder.EntitySet<MyEntity>("MyEntities");
            IEdmModel model = modelBuilder.GetEdmModel();
            return model;
        }

那么这就是正确的配置:

request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntities")));
request.Properties.Add("MS_ODataRouteName", "ODataRoute");

有了这个设置,您的位置标头将被正确生成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接