简体   繁体   English

无法在C#WEB API REST服务单元测试中模拟Owin上下文

[英]Unable to mock Owin context in C# WEB API REST service unit test

I am trying to mock the OWIN context in a C# WEB API REST service but the OWIN context always seems to be null. 我试图在C#WEB API REST服务中模拟OWIN上下文,但OWIN上下文似乎总是为空。 I am using moq and .NET framework 4.5.2. 我正在使用moq和.NET framework 4.5.2。

Here is the controller method I am trying to test: 这是我试图测试的控制器方法:

public class CustomerController : ApiController
{
    // GET: api/Customer/n
    [HttpGet]
    [ClaimsPrincipalPermission(SecurityAction.Demand, Operation = Actions.View, Resource = Resources.Self)]
    public IHttpActionResult Get(int id)
    {
        if (id <= 0)
            return BadRequest();

        ClaimsPrincipalPermission.CheckAccess(id.ToString(),"UserId");

        string jsonResponse = string.Empty;
        HttpStatusCode returnCode = this.ForwardGetRequestToWallet(String.Format("customer/gethistory/{0}", id), out jsonResponse);
        if (returnCode== HttpStatusCode.OK)
            return Ok(jsonResponse);
        return StatusCode(returnCode);            
    } 

And in a helper class the code that goes wrong, on the controller.Request.GetOwinContext call: 并且在一个帮助器类中,出现错误的代码,在controller.Request.GetOwinContext调用:

public static HttpClient GetClient(this ApiController controller)
{
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization = controller.Request.Headers.Authorization;
    var test = controller.Request.GetOwinContext().Request.RemoteIpAddress;
    return client;
} 

My test initialize: 我的测试初始化​​:

[TestInitialize()]
public void MyTestInitialize() 
{
    player_user_id = Convert.ToInt32(ConfigurationManager.AppSettings["PLAYER_USER_ID"]);
    player_user_name = ConfigurationManager.AppSettings["PLAYER_USER_NAME"];
    API_protocol = ConfigurationManager.AppSettings["WEB_API_PROTOCOL"];
    API_host = ConfigurationManager.AppSettings["WEB_API_HOST"];
    API_port = ConfigurationManager.AppSettings["WEB_API_PORT"];

    wrapper = new ClaimsPrincipalWrapper();

    server = new Mock<HttpServerUtilityBase>();
    response = new Mock<HttpResponseBase>();

    request = new Mock<HttpRequestBase>();

    session = new Mock<HttpSessionStateBase>();
    session.Setup(s => s.SessionID).Returns(Guid.NewGuid().ToString());

    config = new HttpConfiguration();

    owinContext = new Mock<IOwinContext>();

    identity = new GenericIdentity(player_user_name);
    identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", player_user_id.ToString()));

    principal = new GenericPrincipal(identity, new[] { "User" });

    context = new Mock<HttpContextBase>();
    context.SetupGet(c => c.Request).Returns(request.Object);
    context.SetupGet(c => c.Response).Returns(response.Object);
    context.SetupGet(c => c.Server).Returns(server.Object);
    context.SetupGet(c => c.Session).Returns(session.Object);
    context.SetupGet(p => p.User.Identity.Name).Returns(player_user_name);
    context.SetupGet(p => p.Request.IsAuthenticated).Returns(true);
    context.SetupGet(s => s.User).Returns(principal);

}

My test: 我的测试:

[TestMethod]
public void Get()
{
    var queryMock = new Mock<IReadableStringCollection>();

    var requestMock = new Mock<IOwinRequest>();

    requestMock.Setup(m => m.Query).Returns(queryMock.Object);

    owinContext.Setup(m => m.Request).Returns(requestMock.Object);

    testURI = new Uri(String.Format("{0}://{1}:{2}/Get/{3}", API_protocol, API_host, API_port, player_user_id));
    request.Setup(r => r.Url).Returns(testURI);

    message = new HttpRequestMessage(HttpMethod.Get, testURI);
    var route = config.Routes.MapHttpRoute("Default", "api/{controller}/{id}");
    var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "customerController", "get" } });

    var testControllerContext = new HttpControllerContext(config, routeData, message);

    var controller = new SharedWalletAPI.Controllers.CustomerController()
    {
        ControllerContext = testControllerContext,
        Request = new HttpRequestMessage(HttpMethod.Get, testURI),
        User = new System.Security.Principal.GenericPrincipal(identity, new string[0])
    };

    // Act
    var _result = controller.Get(player_user_id);

    // get JSON
    //CustomerHistoryList jsonObj = JsonConvert.DeserializeObject<CustomerHistoryList>(_result);

    // Assert
    Assert.IsNotNull(_result);
}

All seems fine until we actually do the get then the OWIN GetOwinContext call which always returns null and hence throws an error of the old chestnut type "Object Reference not set to an instance of an object". 一切似乎都很好,直到我们实际获取然后返回null的OWIN GetOwinContext调用,因此抛出旧的栗子类型“对象引用未设置为对象的实例”的错误。

I am still pretty new to OWIN. 我对OWIN还很新。 Can anyone advise me here? 任何人都可以在这里建议我吗?

First off, I see where you've setup your mock of IOwinContext , but none of the code that you've shown has actually tied your mock into the request. 首先,我看到你在哪里设置了IOwinContext的模拟,但是你所显示的代码中没有一个实际上将你的模拟与请求绑定在一起。 Specifically, there's nothing setup that causes controller.Request.GetOwinContext() to return your mocked object. 具体来说,没有任何设置会导致controller.Request.GetOwinContext()返回您的模拟对象。 So, since you haven't setup an expectation for that call, it will return null. 因此,由于您尚未设置该调用的期望,因此它将返回null。

Testing 测试

The real pain that you're running into is from trying to Mock someone else's very wide interfaces (plural). 你遇到的真正痛苦是试图模仿别人的非常宽的界面(复数)。 That's always a painful thing to do. 这总是很痛苦的事情。 As evidence, look at the amount of code you've had to write to initialize and execute your test. 作为证据,请查看您为初始化和执行测试而必须编写的代码量。 I would try and hide the access to these things behind interfaces that are much more focused and easy to mock / stub / dummy. 我会尝试隐藏对这些东西的访问权限,这些界面更加集中,易于模拟/存根/虚拟。

As much of a unit testing zealot as I am, I don't ever test my WebAPI or MVC controllers in isolation. 作为一个像我一样测试狂热的单元,我不会孤立地测试我的WebAPI或MVC控制器。 They almost always depend on external things that I don't want to dummy up like HttpContext , authentication concerns, method-level attributes. 它们几乎总是依赖于我不想虚拟的外部事物,如HttpContext ,身份验证问题,方法级属性。 Instead, I keep the controllers as thin as possible, mostly limiting the methods to marshalling things in and out of the context, etc. All of the business logic gets moved into classes that the controllers depend on. 相反,我保持控制器尽可能薄,主要是限制方法将内容编组进出内容等等。所有业务逻辑都被移动到控制器所依赖的类中。 Then, those classes are easier to test because they don't depend on things that are a pain to fake. 然后,这些类更容易测试,因为它们不依赖于伪造的东西。

I still write tests that exercise the controller methods, and generally they cover every branch of the controllers. 我仍然编写运行控制器方法的测试,通常它们覆盖控制器的每个分支。 It's just that these are system-level functional tests like automated browser tests for MVC apps or tests that hit the API using an HttpClient for WebAPI apps. 只是这些是系统级功能测试,如MVC应用程序的自动浏览器测试或使用HttpClient for WebAPI应用程序访问API的测试。 If the controllers are kept thin, then there isn't much there to get test coverage on in the first place. 如果控制器保持很薄,那么首先就没有太多的测试覆盖率了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM