Using Moq to Mock an Asynchronous Method for a Unit Test

Using Moq to mock an asynchronous method for a unit test

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.

Setting up a C# Test with Moq against Async methods

You need to fix the set up on the async calls

Moq has a ReturnsAsync that would allow the mocked async method calls to flow to completion.

client
.Setup(_ => _.ReplaceDocumentAsync(It.IsAny<Uri>(), It.IsAny<object>(), It.IsAny<RequestOptions>()))
.ReturnsAsync(new ResourceResponse<Document>());

You typically want to avoid newing up Tasks manually

Mock Async method on Service using Moq

ArtistsControllerTests.Setup() is not being invoked so the mocks are not being setup before the test is exercised.

Therefore when the test is exercised they will return null.

Your setup code is correct, it just is not getting called.

either change that Setup method to a constructor

public ArtistsControllerTests() {
_mockArtistsService.Reset();
_mockPermissionsService
.Setup(service => service.GetPermissionsAsync(It.IsAny<HttpContext>()))
.Returns(Task.FromResult(new Permissions { UserId = "112233", IsAdministrator = false }));
_mockArtistsService.Setup(service => service.GetAllArtists(It.IsAny<string>(), false)).Returns(new ArtistCardDtoCollection());
}

or adorn the method with [TestInitilize] attribute

[TestInitialize]
public void Setup() {
_mockArtistsService.Reset();
_mockPermissionsService
.Setup(service => service.GetPermissionsAsync(It.IsAny<HttpContext>()))
.Returns(Task.FromResult(new Permissions { UserId = "112233", IsAdministrator = false }));
_mockArtistsService.Setup(service => service.GetAllArtists(It.IsAny<string>(), false)).Returns(new ArtistCardDtoCollection());
}

or just move the arrange into the test itself

[TestMethod]
public async Task GetArtists_ReturnsOKStatusCode() {
// arrange
_mockArtistsService.Reset();
_mockPermissionsService
.Setup(service => service.GetPermissionsAsync(It.IsAny<HttpContext>()))
.Returns(Task.FromResult(new Permissions { UserId = "112233", IsAdministrator = false }));
_mockArtistsService.Setup(service => service.GetAllArtists(It.IsAny<string>(), false)).Returns(new ArtistCardDtoCollection());
var artistsController = new ArtistsController(_mockPermissionsService.Object, _mockArtistsService.Object, _mockLogger.Object);
// act
var getArtistsResult = await artistsController.GetArtists();
var okResult = getArtistsResult as OkObjectResult;
// assert
Assert.IsInstanceOfType(okResult, typeof(OkObjectResult));
}

Mocking a method that uses an asynchronous callback with moq

First observation was that you pass in a mocked ISocket in state and try to cast it to Socket in async callback which will result in a null error which means connectDone.Set() is never called so WaitOne will not unblock.

Change that to

private void ConnectCallback(IAsyncResult result) {
ISocket client = (ISocket)result.AsyncState;
client.EndConnect(result);
connectDone.Set();
}

Second observation was that you were not setting up the mocked calls correctly. No need for reflection here as you needed to get the passed arguments from the mock and invoke then in the mock callback setup

The following is based on your original code. Review it to get an understanding of what was explained above.

[TestClass]
public class SocketManagerTests {
[TestMethod]
public void ConnectTest() {
//Arrange
var mockSocket = new Mock<ISocket>();
//async result needed for callback
IAsyncResult mockedIAsyncResult = Mock.Of<IAsyncResult>();
//set mock
mockSocket.Setup(_ => _.BeginConnect(
It.IsAny<EndPoint>(), It.IsAny<AsyncCallback>(), It.IsAny<object>())
)
.Returns(mockedIAsyncResult)
.Callback((EndPoint ep, AsyncCallback cb, object state) => {
var m = Mock.Get(mockedIAsyncResult);
//setup state object on mocked async result
m.Setup(_ => _.AsyncState).Returns(state);
//invoke provided async callback delegate
cb(mockedIAsyncResult);
});

var manager = new SocketManager(mockSocket.Object);

//Act
var actual = manager.Connect();

//Assert
Assert.IsTrue(actual);
mockSocket.Verify(_ => _.EndConnect(mockedIAsyncResult), Times.Once);
}
}

Finally I believe you should consider changing this code to use TPL to get around the whole call back and IAsyncResult drama. Basically exposing an async API and wrapping the calls with a TaskCompletionSource<T> but I guess that is outside of the scope of this question.

Moq Framework to unit test a method that returns a task

Mock returns false, because when you call HttpPost parameters don't match with ones that were set up. The second parameter is different.

You can set up mock like that:

mock
.Setup(x => x.HttpPost("http://test.com", It.IsAny<StringContent>(), "token"))
.Returns(Task.FromResult(true)); //or .ReturnsAsync(true);

It tells mocking framework, that second parameter can be any object of type StringContent.

Docs can be found here: https://github.com/Moq/moq4/wiki/Quickstart#matching-arguments

Unit testing method with async

Are you trying to mock the HttpClient? If so this isn't the recommended approach; instead of mocking HttpClient instantiate a real HttpClient, passing a mocked HttpMessageHandler into the constructor.

Source: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/

Edit: to read the contents of a HTTP response you need to call the response.Content.ReadAsStringAsync() method instead of .ToString().

How do you mock an IAsyncEnumerable?

If you don’t want to do anything special, e.g. a delayed return which is usually the point of async enumerables, then you can just create a generator function that returns the values for you.

public static async IAsyncEnumerable<string> GetTestValues()
{
yield return "foo";
yield return "bar";

await Task.CompletedTask; // to make the compiler warning go away
}

With that, you can simply create a mock for your service and test your object:

var serviceMock = new Mock<IMyService>();
serviceMock.Setup(s => s.CallSomethingReturningAsyncStream()).Returns(GetTestValues);

var thing = new Thing(serviceMock.Object);
var result = await thing.MyMethodIWantToTest();
Assert.Equal("foo", result[0]);
Assert.Equal("bar", result[1]);

Of course, since you are now using a generator function, you can also make this more complicated and add actual delays, or even include some mechanism to control the yielding.

How can I throw Exception for async function using Moq

Considering the asynchronous nature of the code under test, it would be better if the test code be asynchronous as well. Moq is async capable

[Fact]
public async Task CreateCSVFile_Failure() {
//Arrange
var dtData = new DataTable();
string fileName = "";
var mockClient = new Mock<IHttpHandler>();

this._iADLS_Operations = new ADLS_Operations(mockClient.Object);

mockClient
.Setup(repo => repo.PostAsync(It.IsAny<string>(), It.IsAny<HttpContent>(), It.IsAny<string>()))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest));

mockClient
.Setup(repo => repo.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<string>()))
.ThrowsAsync(new Exception("Some message here"));

//Act
Func<Task> act = () => this._iADLS_Operations.CreateCSVFile(dtData, fileName);

//Assert
Exception ex = await Assert.ThrowsAsync<Exception>(act);
Assert.Contains("Exception occurred while executing method:", ex.Message);
}

Note the use of Moq's ReturnsAsync and ThrowsAsync in the setup, along with xUnit's Assert.ThrowsAsync

This now allows you to avoid making blocking calls like .Result which could potentially lead to deadlocks.

Unit Testing Async Controller Method Returning Null with NUnit and Moq

The UserRequest instance you're passing into Authenticate is a different instance than what you're setting up your mock to expect. So, since the inputs don't match, the mock is not executed, and the default mock behavior takes over.

Either remove the mock conditional (i.e., use It.IsAny<UserRequest>()), or pass the same instance to your mock and your controller method.



Related Topics



Leave a reply



Submit