C# – How to unit test async methods

Let’s say you have the following async method you want to test:

public async Task<int> GetSumAsync(int a, int b)

Here’s how to unit test this:

[TestMethod]
public async Task SumTest_WhenInput1And2_Returns3()
{
	//arrange - left blank on purpose

	//act
	var sum = await math.GetSumAsync(1, 2);

	//assert - left blank on purpose
}

This is awaiting the method you’re testing. To await it, you must make the unit test method return async Task.

This example is a bit simplistic. In the real world when you are working with async methods, it typically means you are awaiting results from an external dependency (i.e. a web API, reading from a file, querying a database).

The rest of this article will show the realistic scenario of awaiting file IO and how to unit test this.

Scenario – Asynchronously reading a file and counting unique words

I have a class called FileWordCounter. Given a file path, it reads the file content asynchronously, and then returns the count of unique words in the file.

public class FileWordCounter
{
	public async Task<int> GetUniqueWordCountAsync(string filePath)
}

Constructor inject the IO dependency

FileWordCounter is dependent on file IO operations to get the file contents. I don’t want to hardcode the dependency because then this would be very difficult to test (it would require actually reading from a file).

Therefore I need to constructor inject the dependency, and then await the call on the dependency in the GetUniqueWordCountAsync() method.

IFileIOAsync interface

I need to define an interface for the File IO dependency. This will allow me to mock it out in the unit test.

public interface IFileIOAsync
{
	Task<string> GetFileContentAsync(string filePath);
}

FileWordCounter class

Now I need to pass in the IFileIOAsync dependency to the constructor. This is refered to as Constructor Injection. This will allow me to mock out the dependency in the unit test.

Then in GetUniqueWordCountAsync() i’ll await the await FileIOAsync.GetFileContentAsync() method.

public class FileWordCounter
{
	private readonly IFileIOAsync FileIOAsync;
	public FileWordCounter(IFileIOAsync fileIOAsync)
	{
		FileIOAsync = fileIOAsync;
	}
	public async Task<int> GetUniqueWordCountAsync(string filePath)
	{
		string fileContents = await FileIOAsync.GetFileContentAsync(filePath);
		return new HashSet<string>(fileContents.Split()).Count;
	}
}

Unit testing the async method

In order to unit test the async GetUniqueWordCountAsync() method I need to do two things:

  1. await GetUniqueWordCountAsync() and mark the unit test method to return async Task.
  2. Mock out the async IFileIOAsync.GetFileContentAsync() method. To do this, I’ll use ReturnsAsync() on the mock setup.
[TestMethod()]
public async Task GetUniqueWordCountAsync_When1UniqueWord_Returns1()
{
	//arrange
	string filePath = Guid.NewGuid().ToString();//randomize file path
	int expectedCount = 1;
	string fileContents = "the the the the the";

	Mock<IFileIOAsync> mockFileIOAsync = new Mock<IFileIOAsync>();
	mockFileIOAsync.Setup(t => t.GetFileContentAsync(filePath)).ReturnsAsync(fileContents);

	FileWordCounter fileWordCounter = new FileWordCounter(mockFileIOAsync.Object);

	//act
	var actualCount = await fileWordCounter.GetUniqueWordCountAsync(filePath);

	//assert
	Assert.AreEqual(expectedCount, actualCount);
}

Note: I’m using the Moq mocking framework.

Leave a Comment