Considering the way many tests with xUnit are written, two things need to be improved: the tests need to be more realistic and should prevent having limited logging. Making tests more realistic will make production problems less likely, Moreover, testing what has been logged prevents the problem of having to limited logging details to solve a production issue once it occurs. This article explains how to prevent such problems in your xUnit tests.
Introduction
Production issues are a well-known problem. In most cases, you do not know what could have caused the problem so reading the logs is the first thing to do. After reading the logs, the following questions are likely to be asked within the development team:
- Why isn't the object logged we expect to be logged?
- Why can't we find a warning or error message that describes the problem?
- Why can't we find the exception that must have occurred?
- Why didn't our tests fail since there is clearly something wrong?
From a TDD perspective, two improvements are needed:
- The automated tests need to become more realistic to reproduce real problems.
- Tests need to fail on absence of logging.
This article explains how to do that.
Background
It will be really helpful if you have ever worked with xUnit before since the test examples are based on that. In addition, some experience with .NET Core is helpful too since that is the technology we use here.
Using the code
For demonstration purposes, we use a really simple controller method. The incoming value
contains the FirstName
, MiddleName
and LastName
to return the full name as an ActionResult
.
[HttpPut]
public ActionResult<string> Put([FromBody] Name value)
{
_logger.LogWarning("Warning Logged");
_logger.LogInformation("This is the input {name}", value);
_logger.LogError(new InvalidDataException("Some exception message"),"Some Exception");
return Ok($"{value.FirstName} {value.MiddleName} {value.LastName}");
}
For this method, we need a test that can detect what is logged: an InvalidDataException
, some logging messages and a data object of the type Name
. Moreover, the test needs to be realistic which means that it knows and uses the Startup
class where the dependencies are set. In regular unit tests, this is not the case but in integration tests for .NET Core, this is the case as becomes clear from the code shown here:
[Fact]
public async Task NoMiddleNamePutTest()
{
await using (var fixture = new Fixture<Startup>())
{
var controller = fixture.Create<LogicController>();
var response = controller.Put(new Name()
{
FirstName = "F",
LastName = "L"
});
Assert.Equal(200, ((ObjectResult)response.Result).StatusCode);
Assert.Single(fixture.LogSource.GetWarnings());
var dataLogged = fixture.LogSource.GetLoggedObjects<Name>().ToList();
Assert.Single(dataLogged);
Assert.Equal("F", dataLogged.Single().Value.FirstName);
Assert.Contains(fixture.LogSource.GetLogLines(), a => a == "Warning Logged");
Assert.Contains(fixture.LogSource.GetLogLines(), a => a.Contains("This is the input"));
Assert.Single(fixture.LogSource.GetExceptions().OfType<InvalidDataException>());
}
}
The Fixture<>
class comes from a publicly available NuGet package named IntegrationFixture. It uses the WebApplicationFactory<>
class (explained here) to reduce the boilerplate code to be written by the developer. Real class dependencies set in the Startup
class (not mocks) are used to make the tests realistic. The LogSource
property enables the developer to call methods that return the data we care about (like logged data objects, logged lines and logged exceptions). Production problems are becoming less likely since real dependencies are used so those issues are likely to be covered in the tests. Moreover, if there are production problems, there will be enough logging available to help you resolving the problems since the xUnit tests will simply fail and prevent deployment if there is not enough logging, The code is available on GitHub..
Points of Interest
While writing the code, I started realising more how important automated tests are. As developers, we need to write integration tests for CI purposes, not just unit tests. Moreover, we should not only focus on the required functionality but also on the logging. Having good logging is essential for quickly resolving production issues and with the mentioned NuGet package, we can easily detect what has been logged.
History
- 24th May, 2020: Initial version