Bonus unit testing part 8: Testing async code

Bonus unit testing part 8: Testing async code

·

3 min read

This series of posts accompanies a talk I give about unit testing with the xUnit test framework. Any reader who saw my talk can use this as a reference, others can use this as a starting point to write even better maintainable and reliable code. In my talk, I highlight patterns that work well with easily testable code and combine it all into a working application. All code from these articles can be found in a repository on my GitHub account.

After all the synchronous code I wrote, I wondered how xUnit handles asynchronous code. It is (unsurprisingly) very easy.

Adding an async method

If I want to test asynchronous code, I first need an asynchronous method. I added a LaunchAsync() method in the Launcher class for this purpose.

public Task LaunchAsync()
{
  return Task.Factory.StartNew(() =>
  {
    var missile = missiles.First();
    missiles.Remove(missile);
  });
}

Nothing groundbreaking, but it does what I need it to do. The method starts a task in the background, which will find the first missile from the missiles list and it removes it from said list.

Also note that I use the standard way of indicating that a function is asynchronous by adding 'Async' to the name. This is not necessary, but it does follow clean naming conventions, a very important practice in TDD.

Adding an async [Fact]

Adding a test that is aware of the fact that this code is executed asynchronous is quite easy. All I have to do is change the signature from public void MethodName() to public async Task MethodName() and I'm done. xUnit supports async test methods out of the box. Now I can use the await keyword next to asynchronous functions and await the result.

[Fact]
public async Task When_launching_missile_then_decrease_missile_count_by_1()
{
  // arrange
  var launcher = new Launcher(new List<IMissile> { mockMissile.Object });
  // act
  await launcher.LaunchAsync();
  // assert
  Assert.Equal(0, launcher.MissileCount);
  Assert.True(launcher.MissileCount == 0, "Missile was not launched");
}

I rebuild the solution so the new test is discovered and I'm able to execute the tests as normal.

Adding async [Theory]s

Adding asynchronous data driven tests is just as easy. Fetching the data is handled by the xUnit framework and then the test is run asynchronously with the data passed to it. It works just like non data driven tests as described previously. Below is an example of a data driven test that is loading data via the MemberData attribute.

public static IEnumerable<object[]> MissileData => new[]
{
  new object[] {new HeatSeekingMissile()},
  new object[] {new LaserGuidedMissile()}
};
[Theory]
[MemberData(nameof(MissileData))]
public async Task When_launching_missile_from_memberdata_then_decrease_missile_count_by_1Async(IMissile missile)
{
  // arrange
  var launcher = new Launcher(new List<IMissile> { missile });
  // act
  await launcher.LaunchAsync();
  // assert
  Assert.Equal(0, launcher.MissileCount);
}

Again, all I have to do is rebuild the solution and execute the tests, xUnit takes care of the rest.

Remarks

Although xUnit makes it easy to write tests for asynchronous code, it does not mean it's exempt from race conditions, deadlocks and other asynchronous pitfalls. It is the developers responsibility to make sure that such problems do not occur within the tests they write.

Conclusion

It's is very easy to test asynchronous code with xUnit, so nobody has any excuses to leave some code untested. If anybody notices any inconsistencies, please contact me on twitter or via email.