How to Mock a Task<> Result?

David picture David · May 1, 2014 · Viewed 28.8k times · Source

I'm setting up some unit tests and using Rhino Mocks to populate the object being tested. One of the things being mocked is a Task<HttpResponseMessage>, since the logic being tested includes a call to an HttpClient to get an async response.

So I've started setting up the mocks like this:

var httpClient = MockRepository.GenerateMock<HttpClient>();
var taskFunc = MockRepository.GenerateMock<Func<HttpResponseMessage>>();
var responseTask = MockRepository.GenerateMock<Task<HttpResponseMessage>>(taskFunc);
var response = MockRepository.GenerateMock<HttpResponseMessage>();

httpClient.Stub(c => c.PostAsJsonAsync<IEnumerable<LogMessage>>(Arg<string>.Is.Anything, Arg<IEnumerable<LogMessage>>.Is.Anything)).Return(responseTask);
responseTask.Stub(t => t.Result).Return(response);
response.Stub(r => r.IsSuccessStatusCode).Return(true);

(The "act" step of the test will be to instantiate the object being tested, feed it the httpClient, and run a method on it. The "assert" will verify via the mocks that expected method calls were made on them.)

Stepping through this in a debugger, there's an indefinite hang on this line:

responseTask.Stub(t => t.Result).Return(response);

I don't have a lot of experience with Rhino Mocks or with C# async, so I may be overlooking something obvious. The goal, of course, is that any call to the .Result property would return the response mock. But it looks like my attempt itself is perhaps invoking .Result which I would expect to wait indefinitely since it's just a mock, perhaps?

What is the right way to arrange this? Essentially I need to supply my object with a mocked HttpClient and assert that a method was called on it with a specific argument.

Answer

Lee picture Lee · May 1, 2014

The simplest thing is just to return a completed task with the expected result:

var responseTask = Task.FromResult(response);

I imagine reason this hangs is that the mocked task is never started and hence the given func is not run. You could start it in your test:

var responseTask = MockRepository.GenerateMock<Task<HttpResponseMessage>>(taskFunc);
responseTask.Start();

However there's no reason to mock tasks since you can easily create completed/failed/cancelled tasks directly.