Running xUnit tests on Teamcity using async methods
Asked Answered
G

2

3

I made the following xUnit test which is using a HttpClient to call a status api method on a webserver.

[Fact]
public void AmIAliveTest()
{
    var server = TestServer.Create<Startup>();

    var httpClient = server.HttpClient;
    var response = httpClient.GetAsync("/api/status").Result;

    response.StatusCode.Should().Be(HttpStatusCode.OK);
    var resultString = response.Content.ReadAsAsync<string>().Result;

    resultString.Should().Be("I am alive!");
}

This test is running fine locally. But when I commit the code and try to run the same test on the TeamCity build server, it runs forever. I even have to kill the xunit runner process because stopping the build will not stop this process.

However when I write the test like this

[Fact]
public async void AmIAliveTest()
{
   var server = TestServer.Create<Startup>();

   var httpClient = server.HttpClient;
   var response = await httpClient.GetAsync("/api/status");

   response.StatusCode.Should().Be(HttpStatusCode.OK);
   var resultString = await response.Content.ReadAsAsync<string>();

   resultString.Should().Be("I am alive!");
}

It runs fine locally and also on TeamCity.

My concern is now that I forget to write the test like the second variant and that once in a while the teamcity build is hanging.

Can anybody explain to me why xUnit running on the teamcity buildserver is not running the test correctly in the first place? And is there a solution for this to solve this?

Gamma answered 14/8, 2015 at 8:45 Comment(0)
S
7

Can anybody explain to me why xUnit running on the teamcity buildserver is not running the test correctly in the first place?

First, I'd check your xUnit versions - you should be running the recently-released 2.0. I suspect your local version may be out of date.

The core problem is in this line:

var resultString = response.Content.ReadAsAsync<string>().Result;

I suspect you're running into a deadlock situation that I describe on my blog. HttpClient has some methods on some platforms that do not properly use ConfigureAwait(false), and is thus subject to this deadlock. xUnit 2.0 installs a single-threaded SynchronizationContext into all its unit tests, which provides the other half of the deadlock scenario.

The proper solution is to replace Result with await, and to change the return type of your unit test method from void to Task.

Shanell answered 14/8, 2015 at 13:5 Comment(5)
Thank you so much. My tests were hanging after I added the 8th class with public void methods using .Result. You finished a 8 hour search for the cause of the issue.Boak
I am facing the same issue when running tests in gitlab. I suppose it is not sufficient to just make all the tests use await and return a Task. But, also the code being tested (in a different project) should also avoid using Result anywhere, right?Centeno
@betabandido: Correct.Shanell
Related to this, I tear down the tests by implementing IDisposable in the test class. The problem is I need to delete resources created during the test using a client that has only an async interface. Given I cannot use Result and that the Dispose method cannot return a Task, what is the proper (and safe) way to call an async method in a Dispose method?Centeno
@betabandido: I recommend asking your own question as a question. That will allow a lot of other potential answerers to see it.Shanell
A
-2

Your Tests are broken.

xUnit needs the Task return to be able to figure out that a test failed and more to the point, it is the handle where the exception bubbles back up to xUnit.

By having a public async void, you have an orphaned Task that exceptioned. The resultant exception of course isn't handled, and of course therefore blows up the entire Process. Therefore your test run stops.

You can fix it by writing all your tests like this...

[Fact]
public async Task AmIAliveTest()
{
   var server = TestServer.Create<Startup>();

   var httpClient = server.HttpClient;
   var response = await httpClient.GetAsync("/api/status");

   response.StatusCode.Should().Be(HttpStatusCode.OK);
   var resultString = await response.Content.ReadAsAsync<string>();

   resultString.Should().Be("I am alive!");
}
Assr answered 14/8, 2015 at 8:56 Comment(1)
This is not true. xUnit.net supports "async void" in 2.0+.Syncretize

© 2022 - 2024 — McMap. All rights reserved.