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 = "[email protected]", ToAddress = "[email protected]", CCAddress = "[email protected]", BCCAddress = "[email protected]", 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.
4 Answers
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.
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() { .. .. .. }); 1Try 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()); 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)) );