异步等待在测试中生成空异常

Async Await generating a null exception in testing

我对一些异步代码做了很多大量的修改,而且我一直在修补它足够长的时间以至于我需要第二只眼睛。

在单元测试中,我调用我的异步代码,然后验证它应该 return 的值。最终执行异步操作的对象被模拟。

单元测试:

    public void GetCharactersAsync_ById_Test()
    {
        var charDictionary = TestHelper.GetCharactersIdDictionary(1);
        var mockReq = new Mock<IRequester>();
        mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>
                            (It.IsAny<string>())).ReturnsAsync(charDictionary);
        var char1 = TestHelper.GetCharacter();
        var api = GameObject.GetInstance(mockReq.Object);

        var character = api.GetCharacterAsync(TestHelper.GetRegion(), (int)char.Id);

        Assert.AreEqual(character.Result.Name, char1.Name);
    }

GetCharacterAsync:

    public async Task<Character> GetCharacterAsync(Region region, int charId)
    {
        var res = await requester.CreateGetRequestAsync<Task<Dictionary<long, Character>>>
                       (bld.BuildUrlForEndpoint("GetCharacter",
                       region, charId.ToString()));
        var obj = res.Result.Values.FirstOrDefault();
        return obj;
    }

requester.CreateGetRequestAsync(虽然这应该被嘲笑而不重要):

    public async Task<T> CreateGetRequestAsync<T>(string urlString)
    {
        return await this.GetResultAsyncDeserialized<T>(new HttpRequestMessage(HttpMethod.Get, urlString));
    }

最后是 GetResultAsyncDeserialized(虽然这是模拟的,应该无关紧要):

    protected async Task<T> GetResultAsyncDeserialized<T>(HttpRequestMessage request)

    {
        var result = string.Empty;
        using (var response = await httpClient.GetAsync(request.RequestUri))
        {
            if (!response.IsSuccessStatusCode)
            {
                HandleRequestFailure(response.StatusCode);
            }
            using (var content = response.Content)
            {
                result = await content.ReadAsStringAsync();
            }
        }
        return JsonConvert.DeserializeObject<T>(result);
    }

我对异步的经验几乎为零,这段代码在结构上与旧的工作代码非常相似(我移动了一些东西,比如 JsonConvert 到底层,而它以前是在顶层) .事实上,主要的变化都在被嘲笑的水平上,不应该影响单元测试。单元测试与旧的几乎相同(删除了一些serialization/deserialization)。

Exception is a Null Reference Exception on GetCharacterAsync line:

 var obj = res.Result.Values.FirstOrDefault();

你的 await requester.CreateGetRequestAsync<Task<Dictionary<long, Character>>> 是错误的,你不应该将 Task 传递给那个函数,方法签名应该是 await requester.CreateGetRequestAsync<Dictionary<long, Character>>

public async Task<Character> GetCharacterAsync(Region region, int charId)
{
    var res = await requester.CreateGetRequestAsync<Dictionary<long, Character>>
                   (bld.BuildUrlForEndpoint("GetCharacter",
                   region, charId.ToString()));
    var obj = res.Values.FirstOrDefault();
    return obj;
}

你得到 null 的原因是 GetCharacterAsync 你打电话给 CreateGetRequestAsync<Task<Dictionary<long, Character>>> 但你的最小起订量设置为 mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>


P.S。作为旁注,你真的不应该在你的测试中做 character.Result.Name,进行测试 return async Task 而不是 void 并等待 [=22= 的结果].几乎每个单元测试库(包括 visual studio 中内置的那个)现在都支持 async/await。

public async Task GetCharactersAsync_ById_Test()
{
    var charDictionary = TestHelper.GetCharactersIdDictionary(1);
    var mockReq = new Mock<IRequester>();
    mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>
                        (It.IsAny<string>())).ReturnsAsync(charDictionary);
    var char1 = TestHelper.GetCharacter();
    var api = GameObject.GetInstance(mockReq.Object);

    var character = await api.GetCharacterAsync(TestHelper.GetRegion(), (int)char.Id);

    Assert.AreEqual(character.Name, char1.Name);
}