Why do I get a NullReferenceException when testing this async method with MSpec/Moq?

瘦欲@ 提交于 2019-12-06 00:16:22

This is a slimmed-down version of your spec that works. No NullReferenceException to be seen. Note:

  • The It doesn't check the type of the AwaitResult but rather gets the wrapped Task.Result
  • I don't pass Moq.It<string>.Any... in the Because, that's too much noise. If the parameter is ignored, use a value that communicates that fact.

(Just some text such that the code block below is formatted correctly.)

using System.Diagnostics;
using System.Threading.Tasks;

using Machine.Specifications;

using Moq;

using YourApp;

using It = Machine.Specifications.It;

namespace YourApp
{
  class Foo
  {
  }

  public interface IWebClientWrapper
  {
    Task<string> DownloadStringAsync(string url);
  }

  public class JsonWebServiceClassProvider
  {
    readonly IWebClientWrapper _webClientWrapper;

    public JsonWebServiceClassProvider(IWebClientWrapper webClientWrapper)
    {
      _webClientWrapper = webClientWrapper;
    }

    public async Task<T> GetData<T>(string url) where T : class, new()
    {
      string jsonData = await _webClientWrapper.DownloadStringAsync(url);
      Debug.Assert(jsonData != null);
      return new T();
    }
  }
}

namespace Specs
{
  public class When_calling_GetData_with_a_valid_Json_Service_Url
  {
    const string ValidJson = "{'Nome':'Rogerio','Idade':'51'}";
    static JsonWebServiceClassProvider JsonWebServiceClassProvider;
    static Mock<IWebClientWrapper> Wrapper;
    static AwaitResult<Foo> Result;

    Establish context = () =>
    {
      Wrapper = new Mock<IWebClientWrapper>();
      Wrapper.Setup(w => w.DownloadStringAsync(Moq.It.IsAny<string>()))
             .Returns(Task.FromResult(ValidJson));

      JsonWebServiceClassProvider = new JsonWebServiceClassProvider(Wrapper.Object);
    };

    Because of = () => Result = JsonWebServiceClassProvider.GetData<Foo>("ignored").Await();

    It should_return_a_class_of_same_type = () => Result.AsTask.Result.ShouldBeOfType<Foo>();
  }
}

I'm using this approach with IoC:

1) Creating task runner interface and implementation

public interface ITaskRunner
{
    Task<TNewResult> Execute<TResult, TNewResult>(Func<TResult> action, Func<Task<TResult>, TNewResult> continueWith);
}

public class TaskRunner : ITaskRunner
{
    public Task<TNewResult> Execute<TResult, TNewResult>(Func<TResult> action, Func<Task<TResult>, TNewResult> continueWith)
    {
        return Task.Factory.StartNew(action).ContinueWith(continueWith);
    }
}

and usage:

public Task<JsonResult> CheckForOnline(Int64? adId)
    {
        ITaskRunner taskRunner = IoCFactory.Instance.TryResolve<ITaskRunner>();
        return taskRunner.Execute(() => CheckForOnlineFunc(adId),
            r => Json(r.Result, JsonRequestBehavior.AllowGet));
    }

2) Creating Fake implementation which runs the func in Sync mode (no async calls)

internal class FakeTaskRunner : ITaskRunner
{
    public Task<TNewResult> Execute<TResult, TNewResult>(Func<TResult> action, Func<Task<TResult>, TNewResult> continueWith)
    {
        Task<TResult> task = new Task<TResult>(action);
        try
        {
            task.RunSynchronously();
            if (task.Exception != null)
                throw task.Exception;
            return task.ContinueWith(continueWith);
        }
        catch (Exception ex)
        {
            throw ((AggregateException)ex).InnerExceptions[0];
        }
    }
}

So, same code would run async in live version and async in test. Just make sure to configure IoC to use normal TaskRunner in live and FakeTaskRunner in tests.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!