【问题标题】:Using Moq to mock HttpClient in the context of Thread在 Thread 的上下文中使用 Moq 模拟 HttpClient
【发布时间】:2022-01-28 21:07:27
【问题描述】:

我正在使用moq 创建单元测试,但我无法弄清楚如何将此框架应用到我的程序中处理使用HttpClient 的部分。我发现有各种资源可以演示如何直接模拟 HttpClient 响应,但我的应用程序使用 HttpClient 的方式与使用 Threads 的方式略有不同。

测试的骨架:

public class MyTestClass
{
    public void myTest()
    {
        ClassA classObj = new ClassA();
        classObj.Start();
        // I'd like to use moq somewhere here to mock the response that occurs in DoThreadStuff() below
    }
}

正在测试的类:

public class ClassA
{
    private readonly Thread _myThread; 
    private HttpClient _client;
    
    public ClassA()
    {
        // initialize some values
        _myThread = new Thread(DoThreadStuff);
    }
        
    public void Start()
    {
        _myThread.Start(); // starts DoThreadStuff()
    }
    
    private void DoThreadStuff()
    {
        var newClient = getNewHttpClient(); // utility function returns a HttpClient
        var response = newClient.GetAsync("/my/api/status/endpoint");
    }       
}

如您所见,当ClassA.Start() 被调用时,一个新的HttpClient 被创建并通过GetAsync 使用。为这个构建测试的正确方法是什么?我是否必须更改现有课程的实施以适应最小起订量?有没有人有类似的经验,我可以看看?

【问题讨论】:

  • 您需要模拟 getNewHttpClient 方法以返回正确设置的 HttpClient 模拟。除此之外,您的用例与其他用例没有太大不同。
  • 您可以注入一些工厂来创建 httpclient,这样您就可以模拟工厂(以及 httpclient 本身)。
  • @IsaacPerez 能否分享一下getNewHttpClient的代码?
  • @PeterCsala getNewHttpClient() 只返回一些基本的 HttpClient 对象,然后用于随后的 getAsync() 调用。我认为getNewHttpClient() 的内部结构无关紧要。
  • @PeterCsala 当然,请这样做。我想我不明白

标签: c# unit-testing mocking moq dotnet-httpclient


【解决方案1】:

假设您的ClassA 如下所示:

public class ClassA
{
    private readonly Thread _myThread;

    public ClassA()
    {
        _myThread = new Thread(DoThreadStuff);
    }

    public void Start()
    {
        _myThread.Start();
    }

    private void DoThreadStuff()
    {
        var newClient = getNewHttpClient();

        var response = newClient.GetAsync("https://httpstat.us//200").GetAwaiter().GetResult();
        if(response.StatusCode == HttpStatusCode.OK)
            Console.WriteLine("OK");
        else if(response.StatusCode == HttpStatusCode.InternalServerError)
            Console.WriteLine("Not good");
    }

    protected virtual HttpClient getNewHttpClient()
    {
        return new HttpClient();
    }
}
  • 为了可测试性,我在getNewHttpClient 调用之后添加了一些虚拟代码
    • 请注意,以同步方式 (.GetAwaiter().GetResult()) 调用异步方法并不是一个好主意
  • 我还将 getNewHttpClient 添加到您的课程中作为 protected virtual 以便能够轻松覆盖它

现在让我们创建一个辅助方法来模拟 HttpClient:

public static HttpClient SetupMockClient(HttpResponseMessage response)
{
    var mockMessageHandler = new Mock<HttpMessageHandler>();
    mockMessageHandler.Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(response);
    return new HttpClient(mockMessageHandler.Object);
}

让我们从ClassA 派生来覆盖getNewHttpClient 方法

internal class VerifiableClassA : ClassA
{
    private HttpClient mockedHttpClient;
    public VerifiableClassA(HttpClient mockedHttpClient)
    {
        this.mockedHttpClient = mockedHttpClient;
    }

    protected override HttpClient getNewHttpClient()
    {
        return this.mockedHttpClient;
    }
}

请注意,您可以使用this with moq as well,这样您就不需要仅仅为了测试而引入一个新类。但在这种情况下,getNewHttpClient 应该是 public


现在您可以像这样执行单元测试:

var mockedClient = SetupMockClient(new HttpResponseMessage
{
    StatusCode = HttpStatusCode.InternalServerError,
    Content = new StringContent("Failure")
});

var sut = new VerifiableClassA(mockedClient);
sut.Start();

【讨论】:

  • 谢谢。这真的很有帮助。我遵循了您的设计,当我致电getAsync() 时遇到了异常。异常消息是“线程被中止”。并且 ExceptionState 是“无法评估表达式,因为代码已优化或本机框架位于调用堆栈顶部”。我花了一个小时谷歌搜索,不知道如何解决这个问题。有什么想法吗?
  • @IsaacPerez 我创建了一个简单的 .NET 5 控制台应用程序,上面的代码运行良好,没有任何问题。我不知道您如何使用我的代码,所以我不知道是什么导致了该异常。
  • @IsaacPerez 您是否在 ASP.NET 应用程序中运行代码?这不是一个解决方案,只是一种解决方法,可以查看模拟是否按预期工作:catch (ThreadAbortException ex) { _myThread.ResetAbort(); }
【解决方案2】:

您应该考虑使用工厂/存储库模式。它们使测试您的方法变得更容易,因为您可以只注入像 HttpClient 这样的对象的模拟。 Peter Csala 建议的答案非常肮脏,因为您创建了一个仅用于测试的类。

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
猜你喜欢
  • 1970-01-01
  • 2019-09-27
  • 1970-01-01
  • 2016-08-27
  • 1970-01-01
  • 2011-06-13
  • 2019-09-11
  • 2010-11-16
  • 1970-01-01
相关资源
最近更新 更多