256

I am testing a method for a service that makes a Web API call. Using a normal HttpClient works fine for unit tests if I also run the web service (located in another project in the solution) locally.

However when I check in my changes the build server won't have access to the web service so the tests will fail.

I've devised a way around this for my unit tests by creating an IHttpClient interface and implementing a version that I use in my application. For unit tests, I make a mocked version complete with a mocked asynchronous post method. Here's where I have run into problems. I want to return an OK HttpStatusResult for this particular test. For another similar test I will be returning a bad result.

The test will run but will never complete. It hangs at the await. I am new to asynchronous programming, delegates, and Moq itself and I've been searching SO and google for a while learning new things but I still can't seem to get past this problem.

Here is the method I am trying to test:

public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
    // do stuff
    try
    {
        // The test hangs here, never returning
        HttpResponseMessage response = await client.PostAsync(uri, content);

        // more logic here
    }
    // more stuff
}

Here's my unit test method:

[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
    Email email = new Email()
    {
        FromAddress = "bob@example.com",
        ToAddress = "bill@example.com",
        CCAddress = "brian@example.com",
        BCCAddress = "ben@example.com",
        Subject = "Hello",
        Body = "Hello World."
    };
    var mockClient = new Mock<IHttpClient>();
    mockClient.Setup(c => c.PostAsync(
        It.IsAny<Uri>(),
        It.IsAny<HttpContent>()
        )).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

    bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);

    Assert.IsTrue(result, "Queue failed.");
}

What am I doing wrong?

Thank you for your help.

Sitansu
  • 3,225
  • 8
  • 34
  • 61
mvanella
  • 3,456
  • 3
  • 19
  • 23

4 Answers4

441

You're creating a task but never starting it, so it's never completing. However, don't just start the task - instead, change to using Task.FromResult<TResult> which will give you a task which has already completed:

...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

Note that you won't be testing the actual asynchrony this way - if you want to do that, you need to do a bit more work to create a Task<T> that you can control in a more fine-grained manner... but that's something for another day.

You might also want to consider using a fake for IHttpClient rather than mocking everything - it really depends on how often you need it.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    Re: Fake IHttpClient, I considered that but I needed to be able to return different HttpStatusCodes for different tests based on the expected behavior coming back from the web API, and this seemed to give me more control. – mvanella Dec 31 '13 at 15:55
  • 3
    @mvanella: Yes, so you'd create a fake which can return whatever you want it to. Just something to think about. – Jon Skeet Dec 31 '13 at 16:00
  • 1
    @sgrassie Can you provide a link to some documentation? I can't find any reference to it in the API docs. [link](http://www.nudoq.org/#!/Projects/Moq) – legacybass Dec 16 '14 at 19:27
  • 3
    @legacybass I can't find a link to any documentation for it, even though the API docs says they're built against v4.2.1312.1622 which was [released](http://www.nuget.org/packages/Moq/4.2.1312.1622) almost exactly a year ago. See [this commit](https://github.com/Moq/moq4/commits/master/Source/ReturnsExtensions.cs) which was made a few days before that release. As to why the API documents aren't update... – Stuart Grassie Dec 18 '14 at 10:42
  • @sgrassie Thanks! Weirdly, my DLL from NuGet still shows it under the Moq namespace. Moq is version 4.2.1409.1722. I guess it hasn't moved to the stable release yet. Still, good to know. – legacybass Dec 18 '14 at 18:27
  • 1
    Note: this solution makes synchronous version of asynchronous operation. This may cause code that incorrectly uses async operations (i.e. missing `await`) to pass tests. – Alexei Levenkov May 26 '16 at 16:51
  • @AlexeiLevenkov: Yup, hence "Note that you won't be testing the actual asynchrony this way". For more, we'd need to know what aspects were being tested. – Jon Skeet May 26 '16 at 16:52
  • 1
    Starting with Moq 4.16 you can no longer use the `ReturnsAsync` method. See the [documentation](https://github.com/moq/moq4/wiki/Quickstart#async-methods) for more info. – somethingRandom Jul 12 '22 at 09:58
  • Docs recommend setting up on the `Result` property, i.e. `.Setup(x => x.DoAsync().Result).Returns(...)`. Wondering if there's any hidden pitfalls to either approach. – General Grievance Jun 30 '23 at 11:03
70

Recommend @Stuart Grassie's comment. Use Moq's ReturnsAsync.

var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
                    .Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
                    .ReturnsAsync(new Credentials() { .. .. .. });
General Grievance
  • 4,555
  • 31
  • 31
  • 45
DineshNS
  • 3,490
  • 5
  • 26
  • 28
6

With Mock.Of<...>(...) for async method you can use Task.FromResult(...):

var client = Mock.Of<IHttpClient>(c => 
    c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
Andrii Viazovskyi
  • 777
  • 2
  • 9
  • 15
6

Try using ReturnsAsync. In asynchronous methods it works, I believe the basis to solve your problem should be similar.

_mocker.GetMock<IMyRepository>()
     .Setup(x => x.GetAll())
     .ReturnsAsync(_myFakeListRepository.GetAll());
Kurt Van den Branden
  • 11,995
  • 10
  • 76
  • 85