DEV Community

Cover image for Did you know that you can test the NuGet packages?
Serhii Korol
Serhii Korol

Posted on

Did you know that you can test the NuGet packages?

Anyone who is a Software Engineer familiar with .NET uses NuGet packages. The bigger the solution, the more chaos there is.
In this article, I will explain how to test NuGet packages for the whole solution and describe several real-life cases where it can be useful. And yes, we'll be writing code.

The solution preparing

Before considering different cases, let's create the base solution.
Choose a location on your PC and create a folder where the solution will be placed. Then, go to this folder at once.

mkdir PackageTestSample
cd PackageTestSample
Enter fullscreen mode Exit fullscreen mode

Next, you need to create the empty solution in a previously created folder:

dotnet new sln -n PackageTestSample
Enter fullscreen mode Exit fullscreen mode

Now, I'll create three projects to demonstrate clearly how it works:

  • Create a web API project from a template. Any code there doesn't matter. Remember to add the project to the solution.
dotnet new webapi -n PackageTestSample.AppSample --no-openapi
dotnet sln add PackageTestSample.AppSample/PackageTestSample.AppSample.csproj
cd PackageTestSample.AppSample
Enter fullscreen mode Exit fullscreen mode

For testing, let's add the package at once.

dotnet add package Swashbuckle.AspNetCore --version 6.6.2
Enter fullscreen mode Exit fullscreen mode
  • Create a library project from a template. We won't add the package yet, and the code doesn't matter.
dotnet new classlib -n PackageTestSample.Domain
dotnet sln add PackageTestSample.Domain/PackageTestSample.Domain.csproj
Enter fullscreen mode Exit fullscreen mode
  • Create a test project from the template that contains the xUnit framework. We'll run only this project.
dotnet new xunit -n PackageTestSample.UnitTests
dotnet sln add PackageTestSample.UnitTests/PackageTestSample.UnitTests.csproj
cd PackageTestSample.UnitTests
Enter fullscreen mode Exit fullscreen mode

We need access to the NuGet API to test NuGet packages, so we should install an additional package.

dotnet add package NuGet.Protocol --version 6.12.1
Enter fullscreen mode Exit fullscreen mode

Tests preparing

We can prepare test infrastructure when all projects are created, and all packages are added.
Go to the PackageTestSample.UnitTests and create Models folder:

cd PackageTestSample.UnitTests
mkdir Models
cd Models
Enter fullscreen mode Exit fullscreen mode

Let's create four models that cover crucial package criteria: general information, license information, deprecation, and vulnerability information. Sure, you can join all these models into one, but I split them for the best clarity.

dotnet new class -n PackageInfo
dotnet new class -n PackageLicenseInfo
dotnet new class -n PackageVulnerabilityInfo
dotnet new class -n PackageDeprecationInfo
Enter fullscreen mode Exit fullscreen mode

Please modify these classes:

public class PackageInfo
{
    public required string Project { get; set; }
    public required string NuGetPackage { get; set; }
    public required string Version { get; set; }
}

public class PackageLicenseInfo : PackageInfo
{
    public required string License { get; set; }
}

public class PackageVulnerabilityInfo : PackageInfo
{
    public required IEnumerable<PackageVulnerabilityMetadata> Vulnerabilities { get; set; }
}

public class PackageDeprecationInfo : PackageInfo
{
    public bool IsDeprecated { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

Next, please create a new class where we'll write tests.

dotnet new class -n NuGetPackageTests
Enter fullscreen mode Exit fullscreen mode

Modify this class and add this code:

public class NuGetPackageTests
{
    private static string[] _projectFiles;
    private static string _solutionDirectory;

    public NuGetPackageTests()
    {
        _solutionDirectory = Directory.GetCurrentDirectory()
            .Split("PackageTestSample.UnitTests")
            .First();
        _projectFiles = ListSolutionProjectPaths();
    }


    private static string[] ListSolutionProjectPaths()
    {
        return Directory.GetFiles(
            path: _solutionDirectory,
            searchPattern: "*.csproj",
            searchOption: SearchOption.AllDirectories
        );
    }

}
Enter fullscreen mode Exit fullscreen mode

We define the solution's directory in the constructor and get all existing project paths.

Testing

Now, let's consider crucial cases for necessity test NuGet packages.

Case 1: You need only free or specific licenses.

Since most projects are created for commercial use, you need to control licenses. Some packages can contain restrictions. You can test that your solution has only suited licenses.

Let's add the first test, which tests desired licenses:

[Fact]
    public async Task CheckPackageLicenses()
    {
        //Arrange
        var restrictedNuGetPackages = new List<PackageLicenseInfo>();
        var allowedNugetPackageLicenses = new List<string>
        {
            "MIT",
            "Apache-2.0",
            "Microsoft"
        };

        var resource = await GetNugetResourceAsync();

        //Act
        foreach (var projectFile in _projectFiles)
        {
            foreach (var packageReference in ListNuGetPackages(projectFile))
            {
                var metadata = await GetNuGetMetadataAsync(packageReference, resource);
                var licenseMetadata = metadata.LicenseMetadata?.License ?? metadata.Authors;

                if (allowedNugetPackageLicenses.Contains(licenseMetadata)) continue;
                var nugetPackage = new PackageLicenseInfo
                {
                    NuGetPackage = packageReference.NuGetPackage,
                    Version = packageReference.Version,
                    Project = packageReference.Project,
                    License = licenseMetadata
                };

                restrictedNuGetPackages.Add(nugetPackage);
            }
        }

        //Assert
        Assert.Empty(restrictedNuGetPackages);
    }
Enter fullscreen mode Exit fullscreen mode

This test has errors since we should get NuGet packages from the server.

private async Task<PackageMetadataResource> GetNugetResourceAsync()
    {
        var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");

        return await repository.GetResourceAsync<PackageMetadataResource>();
    }
Enter fullscreen mode Exit fullscreen mode

You still have issues. You should get all installed packages from each project. Please, add this method:

private static PackageInfo[] ListNuGetPackages(string projectFilePath)
    {
        return XDocument
            .Load(projectFilePath)
            .Descendants("PackageReference")
            .Select(packageReference=> new PackageInfo
            {
                Project = projectFilePath.Split('\\').Last().Split(".csproj").First(),
                NuGetPackage = packageReference.Attribute("Include")?.Value ?? string.Empty,
                Version = packageReference.Attribute("Version")?.Value ?? string.Empty
            }).ToArray();
    }
Enter fullscreen mode Exit fullscreen mode

It still isn't working. Since, your project doesn't contain information about license. You need to get package's metadata.

private async Task<IPackageSearchMetadata> GetNuGetMetadataAsync(PackageInfo packageReference, PackageMetadataResource resource)
    {
        var packageIdentity = new PackageIdentity(
            id: packageReference.NuGetPackage,
            version: NuGetVersion.Parse(packageReference.Version)
        );

        return await resource.GetMetadataAsync(
            package: packageIdentity,
            sourceCacheContext: new SourceCacheContext(),
            log: NullLogger.Instance,
            token: default
        );
    }
Enter fullscreen mode Exit fullscreen mode

Let's check it. Go to the PackageTestSample.UnitTests project and run tests:

cd PackageTestSample.UnitTests
dotnet test
Enter fullscreen mode Exit fullscreen mode

The test should be successful.

success license

I won't write a negative test. I just will show this case. If you add a package with a different license like this:

dotnet add package Syncfusion.Licensing --version 27.2.5
Enter fullscreen mode Exit fullscreen mode

As was expected, the test got a fail.

license fail

Case 2: You don't want to use packages with vulnerabilities.

There is a grave reason to check the NuGet packages. A package can become vulnerable later, decreasing your security. You may not even know about it. The test will help you with this.

[Fact]
    public async Task CheckPackageVulnerabilities()
    {
        // Arrange
        var vulnerableNuGetPackages = new List<PackageVulnerabilityInfo>();
        var resource = await GetNugetResourceAsync();

        // Act
        foreach (var projectFile in _projectFiles)
        {
            foreach (var packageReference in ListNuGetPackages(projectFile))
            {
                var metadata = await GetNuGetMetadataAsync(packageReference, resource);
                var vulnerabilities = metadata.Vulnerabilities ?? new List<PackageVulnerabilityMetadata>();

                if (!vulnerabilities.Any()) continue;
                var nugetPackage = new PackageVulnerabilityInfo
                {
                    NuGetPackage = packageReference.NuGetPackage,
                    Version = packageReference.Version,
                    Project = packageReference.Project,
                    Vulnerabilities = vulnerabilities
                };

                vulnerableNuGetPackages.Add(nugetPackage);
            }
        }

        // Assert
        Assert.Empty(vulnerableNuGetPackages);
    }
Enter fullscreen mode Exit fullscreen mode

Information about vulnerabilities can be obtained from metadata.

Check this out. With packages, everything is OK.

package is ok

Now, let's check how it works with existing vulnerabilities. Please, add this package:

dotnet add package Newtonsoft.Json --version 12.0.3
Enter fullscreen mode Exit fullscreen mode

Let's check. How it was expected, we got the fail.

issue

Case 3: You don't want to use deprecated packages.

This test is similar to the previous one. The deprecated packages pose little danger. However, they are potentially vulnerable packages, and you can't update them. You need to add a new package and change the code. The best solution is to control these packages and change them as soon as possible.

[Fact]
    public async Task CheckDeprecatedPackage()
    {
        // Arrange
        var deprecatedPackages = new List<PackageDeprecationInfo>();
        var resource = await GetNugetResourceAsync();

        // Act
        foreach (var projectFile in _projectFiles)
        {
            foreach (var packageReference in ListNuGetPackages(projectFile))
            {
                var metadata = await GetNuGetMetadataAsync(packageReference, resource);
                var tags = metadata.Tags ?? string.Empty;

                if (!tags.Contains("Deprecated")) continue;
                var nugetPackage = new PackageDeprecationInfo
                {
                    NuGetPackage = packageReference.NuGetPackage,
                    Version = packageReference.Version,
                    Project = packageReference.Project,
                    IsDeprecated = (await metadata.GetDeprecationMetadataAsync()).Reasons.Any()
                };

                deprecatedPackages.Add(nugetPackage);
            }
        }

        // Assert
        Assert.Empty(deprecatedPackages);
    }
Enter fullscreen mode Exit fullscreen mode

You can check this.

not deprecated

But if you add an actual deprecated package, you'll get a fail.

dotnet add package EntityFramework.MappingAPI --version 6.2.1
Enter fullscreen mode Exit fullscreen mode

deprecated

Case 4: You have different versions of the same package

I think you have faced a situation where the solution has the same package in different projects but not identical versions. The IDE will throw warnings, but the test will prompt you to fix it. You also can check it. Add this code:

[Fact]
    public void CheckPackageVersionMismatches()
    {
        // Arrange
        var installedNuGetPackages = new List<PackageInfo>();

        foreach (var projectFile in _projectFiles)
        {
            installedNuGetPackages.AddRange(ListNuGetPackages(projectFile));
        }

        // Act
        var packagesToConsolidate = installedNuGetPackages
            .GroupBy(package => package.NuGetPackage)
            .Where(packageGroup => packageGroup.Select(package => package.Version).Distinct().Count() > 1)
            .Select(packageToConsolidate => new
            {
                PackageName = packageToConsolidate.Key,
                Versions = packageToConsolidate.Select(package => $"{package.Project}: {package.Version}")
            }).ToList();

        // Assert
        Assert.Empty(packagesToConsolidate);
    }
Enter fullscreen mode Exit fullscreen mode

Please, check it.

ok

But if I install the package with a higher version to another project, I'll get a fail.

dotnet add package Swashbuckle.AspNetCore --version 7.1.0
Enter fullscreen mode Exit fullscreen mode

fail

Case 5: You want to restrict installing packages.

In this case, the project should stay empty when you do not need any packages. Any developer can add packages by mistake. It would be best if you do not have this issue. Not-used packages require additional memory allocation. In another case, you can control specific packages.

[Fact]
    public void CheckNoInstalledNuGetPackages()
    {
        //Arrange & Act
        var projectFiles = Directory.GetFiles(_solutionDirectory, "*Domain.csproj", SearchOption.AllDirectories);
        var packages = new List<PackageInfo>();

        foreach (var projectFile in projectFiles)
        {
            packages.AddRange(ListNuGetPackages(projectFile).ToList());
        }

        //Assert
        Assert.Empty(packages);
    }
Enter fullscreen mode Exit fullscreen mode

Please check it again.

empty is ok

Install this package for the domain empty project.

dotnet add package Swashbuckle.AspNetCore --version 6.6.2
Enter fullscreen mode Exit fullscreen mode

We got failure.

not empty

Case 6: You need a specific version of the package.

Imagine the situation when you don't update the package because it can influence the entire solution. Or the newest version requires sufficient code changes, and you aren't ready to make them. The test helps you control specific versions against updating.

[Fact]
    public void CheckAllowedVersionsNuGetPackages()
    {
        //Arrange & Act
        var projectFiles = Directory.GetFiles(_solutionDirectory, "*AppSample.csproj", SearchOption.AllDirectories);
        var packages = new List<PackageInfo>();

        foreach (var projectFile in projectFiles)
        {
            packages.AddRange(ListNuGetPackages(projectFile).ToList());
        }

        //Assert
        Assert.Equal("6.6.2", packages.FirstOrDefault(p => p.NuGetPackage == "Swashbuckle.AspNetCore")?.Version);
    }
Enter fullscreen mode Exit fullscreen mode

Let's run it.

version is ok

And now, let's update Swashbuckle.AspNetCore package.

fail version

Conclutions

It's an awesome tool for controlling NuGet packages. You can test specific packages, versions, licenses, and vulnerabilities. It's super easy to use to create tests. I hope this topic was helpful to you. See you later. Happy coding!

Source code: LINK

Buy Me A Beer

Top comments (0)