Marshall Tigerus Marshall Tigerus - 1 month ago 21
C# Question

Async Await generating a null exception in testing

I've done a lot of heavy modification to some async code, and I've been tinkering with it long enough that I am in need of a second set of eyes.

In a unit test, I'm calling my async code, and then validating the values it should return. The object that ultimately performs the async operation is mocked.

The Unit Test:

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 (though this should be mocked and not matter):

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


and finally, GetResultAsyncDeserialized (though this is mocked and shouldn't matter):

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


I have next to zero experience with async, and this code is very similar to the old working code in structure (I moved a few things around, like the JsonConvert to the bottom layer when it used to be up top). The major changes, in fact, were all at the level that is being mocked, and shouldn't impact the unit test. The unit test is almost identical to the old one (removed some serialization/deserialization).

Exception is a Null Reference Exception on GetCharacterAsync line:

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

Answer

Your await requester.CreateGetRequestAsync<Task<Dictionary<long, Character>>> is wrong, you should not be passing Task in to that function, the method signature should be 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;
}

The reason you are getting null is in GetCharacterAsync you call CreateGetRequestAsync<Task<Dictionary<long, Character>>> but your moq was set up with mockReq.Setup(x => x.CreateGetRequestAsync<Dictionary<long, Character>>


P.S. As a side note, you really should not likely be doing character.Result.Name in your test, make the test return async Task instead of void and await the result of api.GetCharacterAsync. Pretty much every unit test library (including the one built in to visual studio) supports async/await now.

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