在使用MSpec / Moq测试此异步方法时,为什么会出现NullReferenceException?

时间:2013-07-06 13:09:39

标签: c# moq async-await mspec

如果从异步方法返回正确的类型,我想测试。此方法在依赖项类中使用另一个异步方法。依赖类实现此接口:

Task<string> DownloadStringAsync(string url);

我想测试的方法是:

public async Task<T> GetData<T>(string url) where T : class , new()
{
    var jsonData = await _webClientWrapper.DownloadStringAsync(url);

    if (string.IsNullOrEmpty(jsonData))
        return new T();

    try
    {
        return await JsonConvert.DeserializeObjectAsync<T>(jsonData);
    }
    catch (JsonException inner)
    {
        throw new JsonConvertException("Error converting Json string", inner) { JsonString = jsonData };
    }
}

使用xUnit和Moq进行测试成功:

public class Testes
{
    private const string ValidJson = "{'Nome':'Rogerio','Idade':'51'}";
    protected static JsonWebServiceClassProvider JsonWebServiceClassProvider;
    private static Mock<IWebClientWrapper> _webClientWrapperMoq;
    private static FakeClassFromJson _resultClass;

    [Fact]
    public async static void When_calling_GetData_it_should_return_a_class_of_same_type()
    {
        _webClientWrapperMoq = new Mock<IWebClientWrapper>();
        _webClientWrapperMoq
            .Setup(w => w.DownloadStringAsync(Moq.It.IsAny<string>()))
            .Returns(Task.FromResult(ValidJson));

        JsonWebServiceClassProvider = new JsonWebServiceClassProvider(_webClientWrapperMoq.Object);

        _resultClass = await JsonWebServiceClassProvider
            .GetData<FakeClassFromJson>(Moq.It.IsAny<string>());

        Assert.IsType<FakeClassFromJson>(_resultClass);
    }
}

使用MSpec和Moq进行测试:

[Subject("JsonWebServiceClassProvider")]
public class When_calling_GetData_with_a_valid_Json_Service_Url
{
    private const string ValidJson = "{'Nome':'Rogerio','Idade':'51'}";
    protected static JsonWebServiceClassProvider JsonWebServiceClassProvider;
    protected static Mock<IWebClientWrapper> WebClientWrapperMoq;
    protected static FakeClassFromJson ResultClass;

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

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

    Because of = () => ResultClass = JsonWebServiceClassProvider
        .GetData<FakeClassFromJson>(Moq.It.IsAny<string>())
        .Await();

    It should_return_a_class_of_same_type = () => ResultClass.ShouldBeOfType<FakeClassFromJson>();
}

这些Because语句

也失败了
Because of = () => JsonWebServiceClassProvider
    .GetData<FakeClassFromJson>(Moq.It.IsAny<string>())
    .ContinueWith(task => ResultClass = task.Result)
    .Wait();

Because of = () => ResultClass = JsonWebServiceClassProvider
    .GetData<FakeClassFromJson>(Moq.It.IsAny<string>())
    .Result;

此行

中的NullReferenceException失败
public async Task<T> GetData<T>(string url) where T : class , new()
{
    string jsonData = await _webClientWrapper.DownloadStringAsync(url);
    // ...
}

解决

在等待回复的同时,做了一些重构并voil!我创建了一个带有Establish语句的基类,并在那里启动了模拟对象:

public class JsonWebServiceClassProviderSpecs
{
    protected static JsonWebServiceClassProvider JsonWebServiceClassProvider;
    protected static Mock<IWebClientWrapper> WebClientWrapperMoq; 

    Establish context = () =>
    {
        WebClientWrapperMoq = new Mock<IWebClientWrapper>();
        JsonWebServiceClassProvider = new JsonWebServiceClassProvider(WebClientWrapperMoq.Object);
    };
}

我更新了测试类:

[Subject("JsonWebServiceClassProvider")]
public class When_ask_data_with_a_valid_Json_Service_Url : JsonWebServiceClassProviderSpecs
{
    private const string ValidJson = "{'Nome':'Rogerio','Idade':'51'}";
    protected static FakeClassFromJson ResultClass;

    Establish context = () =>
    {
        WebClientWrapperMoq
            .Setup(w => w.DownloadStringAsync(Moq.It.IsAny<string>()))
            .Returns(Task.FromResult(ValidJson));
    };

    Because of = () => ResultClass = JsonWebServiceClassProvider
        .GetData<FakeClassFromJson>(Moq.It.IsAny<string>())
        .Await();

    It should_return_a_class_of_same_type = () => ResultClass.ShouldBeOfType<FakeClassFromJson>();
}

2 个答案:

答案 0 :(得分:4)

这是您的规范的精简版本。没有NullReferenceException可见。 注意:

  • It不检查AwaitResult的类型,而是检查包裹的Task.Result
  • 我没有在Moq.It<string>.Any...中传递Because,这太多了。如果忽略该参数,请使用传达该事实的值。

(只是一些文字,以便下面的代码块格式正确。)

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>();
  }
}

答案 1 :(得分:0)

我在IoC中使用这种方法:

1)创建任务运行器界面和实现

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);
    }
}

和用法:

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

2)创建在同步模式下运行func的伪实现(无异步调用)

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];
        }
    }
}

因此,相同的代码将在实时版本中运行async并在测试中运行async。 只需确保将IoC配置为在live和FakeTaskRunner中使用普通的TaskRunner。