我如何单元测试EntitySet控制器

时间:2013-06-05 09:11:53

标签: asp.net asp.net-web-api odata

我尝试对EntitySetController进行单元测试。我可以测试Get但在测试Post方法时遇到问题。

我使用了SetODataPath和SetODataRouteName,但是当我调用this.sut.Post(实体)时,我收到很多关于丢失位置标头,丢失OData-Path,丢失路由的错误。

我在我的智慧结束。 有没有人成功测试他们的EntitySetController?

有人对我有意见吗? 也许我应该只测试我的EntitySetController实现中受保护的覆盖方法?但是我如何测试受保护的方法呢?

感谢您的帮助

5 个答案:

答案 0 :(得分: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_HttpConfigurationMS_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以及如何正确设置我真的不太确定。

答案 1 :(得分:1)

除了@mynameisbob的回答,我发现你也可能需要在Request属性上设置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,上面的方法将抛出一个Argument Null Exception,因为CreateResponse扩展方法试图从HttpRequestContext获取HttpConfiguration(而不是直接从HttpRequest获取)。

答案 2 :(得分: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());

答案 3 :(得分:0)

此外,要获得正确的Location头值等,您真的想要调用Web Api应用程序OData配置代码。

所以而不是使用:

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

您应该将设置OData路由的WebApiConfig类的部分分隔为单独的类(例如ODataConfig),并使用它来为您的测试注册正确的路由:

e.g。

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");

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

答案 4 :(得分: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);
}

这让我花了几天时间拼凑起来,所以我希望这至少可以拯救其他人。