Appendix 1: Commonly Used Libraries with Unit Tests

We have used two major libraries for unit testing across the book:

  • xUnit
  • NSubstitute

Your team may be using these libraries already. Or you may have done a bit of experimentation with unit testing, and you want to expand your horizon into more libraries. While these libraries are popular, other libraries can replace them or work side by side with them. This appendix will skim through the following libraries:

  • MSTest
  • NUnit
  • Moq
  • Fluent Assertions
  • AutoFixture

All these libraries use the MIT license, the most permissive license and you can install any of them via NuGet.

By the end of this appendix, you will be familiar with the libraries that form the ecosystem of unit testing in .NET.

Technical requirements

The code for this chapter can be found at the following GitHub repository:

https://github.com/PacktPublishing/Pragmatic-Test-Driven-Development-in-C-Sharp-and-.NET/tree/main/appendix1

Unit testing frameworks

We have seen xUnit, and we have briefly spoken about MSTest and NUnit. This section will give you a feeling of what these other frameworks are about.

MSTest

MSTest used to be popular, as it was installed as part of Visual Studio (VS) in the older versions of VS. Prior to NuGet’s existence, using a built-in library could cut configuration and deployment time compared to adding and using another framework such as NUnit.

Before NuGet, installing a new library involved manually copying DLLs, putting them in the right location, changing some configurations, and pushing them into source control for the team to share the same files. So, having a pre-installed library and one that didn’t require configuration, such as MSTest, was a blessing. We have moved a long way since then.

To add an MSTest project into your solution, you can do it via the UI:

Figure A1.1 – Adding MSTest via the UI

Figure A1.1 – Adding MSTest via the UI

Notice that there are two copies of the C# version. The bottom one is for the classical .NET Framework, and the top one is what we use with .NET Core.

You can add MSTest via the dotnet CLI:

dotnet new mstest

MSTest and xUnit have similar syntax, so let me show you the code in xUnit and its equivalent in MSTest. I’ll start with xUnit:

public class WeatherAnalysisServiceTests
{
    …
    public WeatherAnalysisServiceTests()
    {
        _sut = new (_openWeatherServiceMock);
    }
    [Fact]
    public async Task GetForecastWeatherAnalysis_
        LatAndLonPassed_ReceivedByOpenWeatherAccurately()
        …
        // Assert
        Assert.Equal(LAT, actualLat);
        Assert.Equal(LON, actualLon);
    }
    …

The equivalent in MSTest is as follows:

[TestClass]
public class WeatherAnalysisServiceTests
{
    …
    [TestInitialize]
    public void TestInitialize()
    {
        _sut = new(_openWeatherServiceMock);
    }
    [TestMethod]
    public async Task GetForecastWeatherAnalysis_
      LatAndLonPassed_ReceivedByOpenWeatherAccurately()
    {
        …
        // Assert
        Assert.AreEqual(LAT, actualLat);
        Assert.AreEqual(LON, actualLon);
    }
    …

You can directly spot a few differences between the two code snippets:

  • The unit test class in MSTest has to be decorated with TestClass.
  • The constructor in MSTest would work, but the standard way of initializing is to decorate a method with TestInitialize.
  • Both libraries use the Assert class name, but the method names in the class are different; for example, xUnit uses Equal and True, while MSTest uses AreEqual and IsTrue.

When doing multiple tests, xUnit and MSTest use different attributes. This code is in xUnit:

[Theory]
[InlineData("Freezing", -1)]
[InlineData("Scorching", 46)]
public async Task GetForecastWeatherAnalysis_
   Summary_MatchesTemp(string summary, double temp)
{
…

In MSTest, the equivalent code will look like this:

[DataTestMethod]
[DataRow("Freezing", -1)]
[DataRow("Scorching", 46)]
public async Task GetForecastWeatherAnalysis_
   Summary_MatchesTemp(string summary, double temp)
{
…

Here, you can notice two differences:

  • Theory becomes DataTestMethod.
  • InlineData becomes DataRow.

As you can see, there isn’t much difference between the two libraries. Also, executing the test running, running the Test Explorer and other test activities other than the code stay the same.

NUnit

NUnit used to be the dominant library in the first decade of the two-thousands; it is still in use today, with xUnit becoming more prevalent.

To add an NUnit project to your solution, you can do it via the UI:

Figure A1.2 – Adding NUnit via the UI

Figure A1.2 – Adding NUnit via the UI

Just like MSTest, NUnit has two copies of the .NET version. The bottom one is for the classical .NET Framework, and the top one is what we use with .NET Core.

You can add NUnit via the dotnet CLI:

dotnet new nunit

NUnit and xUnit have similar syntax, so let me show you the code in NUnit and its equivalent in MSTest. I’ll start with xUnit:

public class WeatherAnalysisServiceTests
{
    …
    public WeatherAnalysisServiceTests()
    {
        _sut = new (_openWeatherServiceMock);
    }
    [Fact]
    public async Task GetForecastWeatherAnalysis_
        LatAndLonPassed_ReceivedByOpenWeatherAccurately()
        …
        // Assert
        Assert.Equal(LAT, actualLat);
        Assert.Equal(LON, actualLon);
    }
    …

The equivalent in MSTest is as follows:

public class WeatherAnalysisServiceTests
{
    …
    [Setup]
    public void Setup()
    {
        _sut = new(_openWeatherServiceMock);
    }
    [Test]
    public async Task GetForecastWeatherAnalysis_
      LatAndLonPassed_ReceivedByOpenWeatherAccurately()
    {
        …
        // Assert
        Assert.That(actualLat, Is.EqualTo(LAT));
        Assert.That(actualLon, Is.EqualTo(LON));
    }
    …

You can directly spot a few differences between the two code snippets:

  • The constructor in NUnit would work, but the standard way of initializing is to decorate a method with Setup.
  • Both libraries use the Assert class name, but the method names in the class are different; for example, xUnit uses Equal, while NUnit uses AreEqual.
  • The style of NUnit uses a fluent interface design, and the recommended approach to test equality is to use That and Is.EqualTo.

When doing multiple tests, xUnit and NUnit use different class names. This code is in xUnit:

[Theory]
[InlineData("Freezing", -1)]
[InlineData("Scorching", 46)]
public async Task GetForecastWeatherAnalysis_
   Summary_MatchesTemp(string summary, double temp)
{
…

In NUnit, the equivalent code will look like this:

[Theory]
[TestCase("Freezing", -1)]
[TestCase("Scorching", 46)]
public async Task GetForecastWeatherAnalysis_
   Summary_MatchesTemp(string summary, double temp)
{
…

Here, you can notice that InlineData becomes TestCase. Besides this, there isn’t much difference between the two libraries, and their template is included in the default installation of VS 2022.

These three libraries are interchangeable, and the syntax changes are minimal. Once you are used to one, it will take minimal time to switch to the other.

Mocking libraries

There is no shortage of mocking libraries in .NET; however, the top two used libraries are NSubstitute and Moq. We have covered plenty of examples of NSubstitute in this book, so let’s see how Moq works.

Moq

Moq has the same role and same functionality, more or less, as NSubstitute. Given that the book was using NSubstitute, the fastest way to introduce Moq is to compare the two libraries. Let’s start with a snippet from NSubstitute:

private IOpenWeatherService _openWeatherServiceMock = 
    Substitute.For<IOpenWeatherService>();
private WeatherAnalysisService _sut;
private const decimal LAT = 2.2m;
private const decimal LON = 1.1m;
public WeatherAnalysisServiceTests()
{
    _sut = new (_openWeatherServiceMock);
}
[Fact]
public async Task GetForecastWeatherAnalysis_
    LatAndLonPassed_ReceivedByOpenWeatherAccurately()
{
    // Arrange
    decimal actualLat = 0;
    decimal actualLon = 0;
    _openWeatherServiceMock.OneCallAsync(
        Arg.Do<decimal>(x => actualLat = x),
        Arg.Do<decimal>(x => actualLon = x),
        Arg.Any<IEnumerable<Excludes>>(), 
        Arg.Any<Units>())
       .Returns(Task.FromResult(GetSample(_defaultTemps)));
    // Act
    await _sut.GetForecastWeatherAnalysis(LAT, LON);
    // Assert
    Assert.Equal(LAT, actualLat);
    Assert.Equal(LON, actualLon);
}

This snippet instantiates and creates a mock object from IOpenWeatherService and spies on the lat and lon input parameters of OneCallAsync. The idea is to ensure the two parameters passed to GetForecastWeatherAnalysis are passed without modification to the OneCallAsync method.

Let’s look at the same code using Moq:

private IOpenWeatherService _openWeatherServiceMock = 
    Mock.Of<IOpenWeatherService>();
private WeatherAnalysisService _sut;
private const decimal LAT = 2.2m;
private const decimal LON = 1.1m;
public WeatherAnalysisServiceTests()
{
    _sut = new (_openWeatherServiceMock);
}
[Fact]
public async Task GetForecastWeatherAnalysis_
    LatAndLonPassed_ReceivedByOpenWeatherAccurately()
{
    // Arrange
    decimal actualLat = 0;
    decimal actualLon = 0;
    Mock.Get(_openWeatherServiceMock)
        .Setup(x => x.OneCallAsync(It.IsAny<decimal>(),
        It.IsAny<decimal>(), 
        It.IsAny<IEnumerable<Excludes>>(),
        It.IsAny<Units>()))
        .Callback<decimal, decimal, 
        IEnumerable<Excludes>, Units>((lat, lon, _, _) => {
            actualLat = lat; actualLon = lon; })
       .Returns(Task.FromResult(GetSample(_defaultTemps)));
    // Act
    await _sut.GetForecastWeatherAnalysis(LAT, LON);
    // Assert
    Assert.Equal(LAT, actualLat);
    Assert.Equal(LON, actualLon);
}

This Moq code doesn’t look much different from its NSubstitute rival. Let’s analyze the differences:

  • NSubstitute instantiates a mock object using the Substitute.For method, whereas Moq does it using Mock.Of.
  • NSubstitute uses extension methods such as Returns to configure the mock object, whereas Moq doesn’t use extensions.
  • NSubstitute uses Args.Any to pass parameters, whereas Moq uses It.IsAny.

In general, while Moq favors syntax with lambda expressions, NSubstitute takes another route and uses extension methods. NSubstitute tries to make the code look as natural as possible and get out of the way by having less syntax, while Moq relies on the power of lambdas.

Important note

Moq has another way of creating a mock. I opted to show the modern version.

In my opinion, using one library or another is a matter of style and syntax preference.

Unit testing helper libraries

I have seen developers adding these two libraries to their unit tests to enhance the syntax and readability: Fluent Assertions and AutoFixture.

Fluent Assertions

Fluent implementation, also known as a fluent interface, is trying to make the code read like an English sentence. Take this example:

Is.Equal.To(…);

Some developers like to have the tests written in this way as it supports a more natural way of reading a test. Some like it for their own reasons.

FluentAssertions is a popular library that integrates with all popular test frameworks among MSTest, Nunit, and xUnit to enable fluent interfaces. You can add it to your unit test project via NuGet under the name FluentAssertions.

Let’s see how our code will be without and with the library:

// Without
Assert.Equal(LAT, actualLat);
// With
actualLat.Should().Be(LAT);

But the previous snippet doesn’t show the true power of the library, so let’s do some other examples:

// Arrange
string actual = "Hi Madam, I am Adam";
// Assert actual.Should().StartWith("Hi")
    .And.EndWith("Adam")
    .And.Contain("Madam")
    .And.HaveLength(19);

The previous snippet is an example of a fluent syntax, and the code is self-explanatory. To test this code, you will require a few lines of the standard Assert syntax.

Here is another example:

// Arrange
var integers = new int[] { 1, 2, 3 };
// Assert
integers.Should().OnlyContain(x => x >= 0);
integers.Should().HaveCount(10, 
  "The set does not contain the right number of elements");

The previous code is also self-explanatory.

Important Note

While these code snippets show the power of FluentAssertions, asserting too many unrelated elements in a unit test is not recommended. These examples are for illustration only and do not focus on best unit testing practices.

These two code snippets are enough to show why some developers are fond of this syntax style. Now that you know about it, the choice of using such syntax is yours.

AutoFixture

Sometimes, you have to generate data to populate an object. The object may be directly related to your unit test. Or maybe you just want to populate it for the rest of the unit to execute, but it is not the subject of the test. This is when AutoFixture comes to the rescue.

You can write the tedious code to generate an object, or you can use AutoFixture. Let’s illustrate this with an example. Consider the following record class:

public record OneCallResponse
{
    public double Lat { get; set; }
    public double Lon { get; set; }
    …
    public Daily[] Daily { get; set; }
}
public record Daily
{
    public DateTime Dt { get; set; }
    public Temp Temp { get; set; }
    …
}
// More classes

Populating this in the Arrange part of your unit test will increase the size of your unit test and distract the test from its real intention.

AutoFixture can create an instance of this class using the least amount of code:

var oneCallResponse = _fixture.Create<OneCallResponse>();

This will create an object of this class and populate it with random values. These are some of the values:

{OneCallResponse { Lat = 186, Lon = 231, Timezone = Timezone9d27503a-a90d-40a6-a9ac-99873284edef, TimezoneOffset = 177, Daily = Uqs.WeatherForecaster.Daily[] }}
    Daily: {Uqs.WeatherForecaster.Daily[3]}
    EqualityContract: {Name = "OneCallResponse" FullName = 
        "Uqs.WeatherForecaster.OneCallResponse"}
    Lat: 186
    Lon: 231
    Timezone: "Timezone9d27503a-a90d-40a6-a9ac-99873284edef"
    TimezoneOffset: 177

The previous output is the first level of the OneCallResponse class, but all the succeeding levels are also populated.

But what if you want fine control of the generated data? Let’s say we want to generate the class, but with the Daily property having an array length of 8 rather than a random size:

var oneCallResponse = _fixture.Build<OneCallResponse>()
  .With(x => x.Daily,_fixture.CreateMany<Daily>(8).ToArray())
  .Create(); 

This will generate everything randomly, but the Daily property will have eight array elements with random values.

This library has plenty of methods and customizations; this section only scratches the surface.

This appendix is a brief pointer to several libraries used for, or in conjunction with, unit testing. The intention here is to tell you these libraries exist and intrigue you to dig further if the need arises.

Further reading 

To learn more about the topics discussed in the chapter, you can refer to the following links:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.252.238