'Using Moq to mock HttpClient in the context of Thread

I'm working on creating unit tests using moq and I'm having trouble figuring out how to apply this framework to a portion of my program that deals with using an HttpClient. There's various resources I found that demonstrate how to mock an HttpClient response directly but the way my application makes use of HttpClient is slightly different with the utilization of Threads.

The test's skeleton:

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
    }
}

The class under testing:

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

As you can see, when ClassA.Start() gets called, a new HttpClient gets created and used via GetAsync. What would the correct way to structure a test for this look like? Will I have to change the implementation of my existing classes to accommodate for Moq? Does anyone have experience with something very similar which I could take a look at?



Solution 1:[1]

Let's suppose that your ClassA looks like this:

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();
    }
}
  • For the sake of testability I've added some dummy code after the getNewHttpClient call
    • Please note that calling an async method in sync fashion (.GetAwaiter().GetResult()) is not really a good idea
  • I've also added the getNewHttpClient to your class as protected virtual to be able to overwrite it easily

Now let's create a helper method to be able to mock an 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);
}

Let's derive from the ClassA to override the getNewHttpClient method

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

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

Please note that you can do this with moq as well so you don't need to introduce a new class just for testing. But in that case the getNewHttpClient should be public.


Now you can perform unit testing like this:

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

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

Solution 2:[2]

You should consider to use a Factory/Repository-Pattern. They make it easier to Test your methods because you can just inject mocks of objects like HttpClient. The answer Peter Csala suggested is pretty dirty because you create a class that has the single purpose to be used in a test.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peter Csala
Solution 2 JanickPreun