1

I have a method that should make a http call and should wait for response no longer that some timeout. If no response received within timeout time method should return empty string, BUT response must be processed later anyway.

    async Task<string> GetResponseOrContinue(int timeout, Action<string> responseHandler)
    {
        var task = Task.Run(async () =>
        {
            var response = await _webClient.UploadStringTaskAsync("url", "");
            responseHandler(response);
            return response;
        });

        var emptyTask = Task.Run(async () =>
        {
            await Task.Delay(timeout);
            return string.Empty;
        });

        return await await Task.WhenAny(task, emptyTask);
    }

I omitted try/catches and other details for the sake of simplicity... This code seems to work but I stuck trying to test it.

I wrote test that works correctly almost always, but sometimes something goes wrong with this timeout stuff and test fails.

I mock UploadStringTaskAsync call using Moq framework:

    certWebClient.Setup(x => x.UploadStringTaskAsync(It.IsAny<string>(), It.IsAny<string>()))
        .ReturnsAsync(() =>
        {
            Thread.Sleep(timeout + 10);
            return "this response will be received after timeout";
        });

and then pass this timeout variable to GetResponseOrContinue method. Looks pretty simple but it's not trustworthy... What can go wrong here and maybe there more appropriate solution for such problem?

Akmal Salikhov
  • 818
  • 3
  • 12
  • 25
  • Do no use `Thread.Sleep` in async code, `await Task.Delay(timeout + 10)` is preferred way – Pavel Anikhouski May 12 '20 at 20:25
  • @PavelAnikhouski I tried it with `await Task.Delay(doPaymentTimeout + 1)` too but with no success... – Akmal Salikhov May 12 '20 at 20:31
  • Does https://stackoverflow.com/questions/49338398/async-unit-testing-with-nunit-and-c-sharp help? The idea is to use an `async Task` method in `[Test]` so that you can *await* the call to `GetResponseOrContinue`. – Arun M May 13 '20 at 02:02
  • CancellationToken may be a better pattern to support timeouts of async calls. See https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/cancel-async-tasks-after-a-period-of-time – Arun M May 13 '20 at 02:04

1 Answers1

1

One way to unit test this kind of code is to abstract away everything that has to do with threads, tasks, timers, datetimes etc. You could for example inject a special task-factory that returns tasks that may be completed explicitly by your test.

This makes both the code and the tests a bit more complex to write. So I would consider cost/benefit of any particular test.

JonasH
  • 28,608
  • 2
  • 10
  • 23