【问题标题】:C# unit testing API 2 callC# 单元测试 API 2 调用
【发布时间】:2014-09-19 15:31:03
【问题描述】:

我有一个 web api 2 web 服务获取方法。在里面我使用的是 HttpContext.Current.Request.UserHostAddress。当直接在他的单元测试中调用我的控制器方法时,这没有填写,所以空对象的错误也是如此。因此,我搜索了如何填写此内容并发现以下内容有助于解决该问题:Add IP address to HttpRequestMessage

但是,这需要一个服务器名称才能将请求发送到。问题是,当测试运行时,需要为这个 API Web 服务运行 VSExpress,而在运行测试时不需要。最重要的是,即使它似乎选择了一个随机端口来运行,所以我不能像他在上面的链接中那样对地址进行硬编码。鉴于上述问题,我如何测试我的 api 2 方法?

这是我刚测试api方法时爆炸的那一行

string ip = HttpContext.Current.Request.UserHostAddress;

[编辑]回答

让每个人都知道这里是代码中的解决方案

public class MyController : ApiController
{
    private: HttpRequestBase httpRequest;

    public MyController()
    {
        httpRequest = new HttpRequestWrapper(HttpContext.Current.Request)
    }

    public MyController(HttpRequestBase http)
    {
        httpRequest = http;
    }

    public HttpResponseMessage Get()
    {
        string ip = httpRequest.UserHostAddress;
    }
}

我在单元测试中使用 Moq:

Mock<HttpRequestBase> httpRequestMock = new Mock<HttpRequestBase>();

httpRequestMock.Setup(x => x.UserHostAddress).Returns("127.0.0.1");

// then pass httpRequestMock.Object to my controller ctor and good to go

【问题讨论】:

  • 如果您愿意更改您的代码 - 只需将“给我用户 IP”功能作为您的类的依赖项(即,如果您使用某些 DI 框架,则将 IGiveMeRequestInformation 之类的接口传递给控制器​​的构造函数) 并模拟它进行测试。

标签: c# unit-testing asp.net-web-api2


【解决方案1】:

将控制器与 HTTP 上下文分离。可能有一些我不熟悉的内置功能可以做到这一点,但一种方法是简单地注入一个可模拟对象。考虑这样的事情:

public interface IRequestInformation
{
    string UserHostAddress { get; }
}

public class RequestInformation : IRequestInformation
{
    public string UserHostAddress
    {
        get { return HttpContext.Current.Request.UserHostAddress; }
    }
}

现在您已经抽象出接口后面对HttpContext 的依赖。如果您使用依赖注入,请将该接口注入您的控制器。如果你不是,你可以伪造它:

// in your controller...
private IRequestInformation _request;
public IRequestInformation RequestInfo
{
    get
    {
        if (_request == null)
            _request = new RequestInformation();
        return _request;
    }
    set { _request = value; }
}

然后在你的控制器逻辑中使用它:

string ip = RequestInfo.UserHostAddress;

现在在您的单元测试中,您可以提供模拟/假/等。对于RequestInfo 属性。手动创建一个或使用模拟库。如果您手动创建一个,那就很简单了:

public class RequestInformationFake : IRequestInformation
{
    public string UserHostAddress
    {
        get { return "some known value"; }
    }
}

然后在安排测试时将其提供给控制器:

var controller = new YourController();
controller.RequestInformation = new RequestInformationFake();
// run your test

【讨论】:

    【解决方案2】:

    将您对HttpContext 的引用替换为对HttpContextBase 的引用。在您的代码中,使用HttpContextWrapper 实例初始化HttpContextBase,这是Web 堆栈中的默认行为实现。

    但是,在您的测试中注入一个自定义的 HttpContextBase 实现,您可以在其中实现仅测试所需的方法和行为。

    如链接中所述:

    HttpContextBase 类是一个抽象类,包含相同的 成员作为 HttpContext 类。 HttpContextBase 类启用 您可以创建类似于 HttpContext 类的派生类,但是 您可以自定义并在 ASP.NET 管道之外工作。 执行单元测试时,通常使用派生类 实现具有满足场景的自定义行为的成员 你正在测试。

    【讨论】:

    • 我喜欢这个想法,因为它的代码更少,但我无法编译它。我在我的方法中创建了一个变量: private HttpContextBase httpContext;然后在我的产品控制器中,我尝试将其设置为当前值,但它给出了无法转换的编译错误,这看起来很奇怪,因为我应该能够将子对象存储在我认为的基本引用中。我必须手动转换它还是会导致我不知道的问题?失败的行是 httpContext = HttpContext.Current;
    • HttpContext 不继承自 HttpContextBase,所以必须使用httpContext = new HttpContextWrapper(HttpContext.Current)。理想情况下,您可以使用 IOC 框架对这些值进行事件注入,但您始终可以在构建时初始化上下文
    • 哦,那是一个真正的 .NET 类。听起来更像是你想让我做的一门课。奇怪的是,鉴于他们的名字,它们之间并没有关联。谢谢。
    • 是的,自 .net 3.5 以来的实际课程:msdn.microsoft.com/en-us/library/…
    【解决方案3】:

    将以下方法添加到控制器,或注入等效方法。它使用魔术字符串 MS_HttpContext,因为这正是 AspNetWebStack implementation 用于完全相同目的的字符串。

    HttpContextBase HttpContextBase => HttpContext.Current != null
        ? new HttpContextWrapper(HttpContext.Current)
        : (HttpContextBase)Request.Properties["MS_HttpContext"]
    

    将控制器中HttpContext.Current 的所有其他用途替换为HttpContextBase

    单元测试时:

    var context = new Mock<HttpContextBase>();
    ...
    
    controller.Request = new HttpRequestMessage();
    controller.Request.Properties["MS_HttpContext"] = context.Object;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-04
      • 2021-11-09
      • 2018-04-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多