Reputation: 20731
I am writing automated tests for a large application.
Some of these tests could easily be async
methods, as they make use of an HttpClient
, which offers only async
methods for issuing requests.
Some crucial aspects of the application to test are the following:
Now, my problem is that when using await
in an async
method, the continuation might happen in a different thread than before (as described in answers such as this or this). Obviously, the other thread doesn't have the aforementioned cached context information and I'm immediately drowning in exceptions as soon as I invoke anything.
Now, if I use GetAwaiter().GetResult()
(as suggested here) instead of await
on the HTTP calls and make my test methods synchronous, everything appears to work fine for my test cases.
Will this solution reliably ensure my entire test case will run on the same thread? Or do I have to pick one of the seemingly more complex solutions, such as implementing a custom synchronization context?
EDIT: To be clear: Changing the architecture of the application core itself, as questionable as its design may or may not be, is not an option.
To be clear no. 2: The only async
methods I would like to call are the ones on HttpClient
for sending HTTP requests. Our own code does not contain any async
methods (possibly, for the reasons described above).
EDIT2: Here's a simplified version of my test method, to give an idea of what kinds of calls are in there:
[Test]
public async Task TestWriteAsync()
{
MyAppLogic.Initialize(TestConstants.TestUserId);
MyAppLogic.CleanUp();
using (var client = new HttpClient())
{
var content = ... // my input data
var response = await client.PostAsync("http://my.web.app.com/application/write", content);
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.AreEqual(..., MyAppLogic.GetValue());
MyAppLogic.CleanUp();
}
}
Tentative rewrite that appears to work:
[Test]
public void TestWrite()
{
MyAppLogic.Initialize(TestConstants.TestUserId);
MyAppLogic.CleanUp();
using (var client = new HttpClient())
{
var content = ... // my input data
var response = client.PostAsync("http://my.web.app.com/application/write", content).GetAwaiter().GetResult();
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.AreEqual(..., MyAppLogic.GetValue());
MyAppLogic.CleanUp();
}
}
Upvotes: 0
Views: 166
Reputation: 61666
In its core, it relies heavily on static objects that cache context information (e.g. the entire permission scheme of the active user) per thread.
Perhaps, you can make those static object to be AsyncLocal<T>
(versus ThreadLocal<T>
or [ThreadStatic]
). Then they will flow across different threads.
If though you're only allowed to touch the tests and not the code under test, then you'd have to stick with synchronous methods, as Marc mentioned in the comments.
You should still be able to use HttpClient.PostAsync(..).GetAwaiter().GetResult()
, instead of HttpWebRequest
. If you're concerned that something inside HttpClient
may end up in a deadlock, you can wrap the call with Task.Run
:
Task.Run(() => client.PostAsync(..)).GetAwaiter().GetResult()
Note however, there's no thread affinity in ASP.NET environment, i.e. your ASP.NET controller methods would most likely be called each time on a different thread. So, those static per-thread objects might only be valid for a scope of a specific HTTP request/response.
Upvotes: 2