Michael Whelan

behaviour driven blog

Semantic Versioning Tests with PublicApiGenerator

When you are following semantic versioning for the public API of your software package, it can be quite easy to accidentally change the API. Jake Ginnivan wrote the PublicApiGenerator package to generate the public API of your package as a string. That way, you can write a unit test that verifies the contents of the string hasn't changed, using an approval test framework, such as Shouldly or ApprovalTests.

The first thing that you need to do is install the Public Api Generator package into your test project.

Install-Package PublicApiGenerator

Then write a unit test, calling the GetPublicApi method on the static PublicApiGenerator class, passing in the assembly that you want to generate the API for.

[Test]
public void specify_autofac_has_no_public_api_changes()
{
    var publicApi = PublicApiGenerator.PublicApiGenerator.GetPublicApi(typeof(AutofacContainer).Assembly);
    publicApi.ShouldMatchApproved();
}

The test then calls Shouldly's ShouldMatchApproved method, which generates a text file with the contents of the public API string in the form [ClassName].[TestName].received.txt. For example, in this case the file name is SemanticVersioningTests.specifyautofachasnopublicapichanges.received.txt.

[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("Specify.IntegrationTests")]
[assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)]
[assembly: System.Runtime.InteropServices.GuidAttribute("2cb27234-4675-4f6b-b165-052ff7e5fa5e")]
[assembly: System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.0", FrameworkDisplayName=".NET Framework 4")]

namespace Specify.Autofac
{  
    public class AutofacContainer : Specify.IContainer, System.IDisposable
    {
        protected Autofac.ContainerBuilder _containerBuilder;
        public AutofacContainer() { }
        public AutofacContainer(Autofac.ILifetimeScope container) { }
        public AutofacContainer(Autofac.ContainerBuilder containerBuilder) { }
        public Autofac.ILifetimeScope Container { get; }
        public bool CanResolve<T>()
            where T :  class { }
        public bool CanResolve(System.Type serviceType) { }
        public void Dispose() { }
        public T Get<T>(string key = null)
            where T :  class { }
        public object Get(System.Type serviceType, string key = null) { }
        public void Set<T>()
            where T :  class { }
        public void Set<TService, TImplementation>()
            where TService :  class
            where TImplementation :  class, TService { }
        public T Set<T>(T valueToSet, string key = null)
            where T :  class { }
    }
    public class AutofacMockRegistrationHandler : Autofac.Core.IRegistrationSource
    {
        public AutofacMockRegistrationHandler(Specify.Mocks.IMockFactory mockFactory) { }
        public bool IsAdapterForIndividualComponents { get; }
        public System.Collections.Generic.IEnumerable<Autofac.Core.IComponentRegistration> RegistrationsFor(Autofac.Core.Service service, System.Func<Autofac.Core.Service, System.Collections.Generic.IEnumerable<Autofac.Core.IComponentRegistration>> registrationAccessor) { }
    }
    public class DefaultAutofacBootstrapper : Specify.Configuration.BootstrapperBase
    {
        public DefaultAutofacBootstrapper() { }
        protected override Specify.IContainer BuildApplicationContainer() { }
        public virtual void ConfigureContainer(Autofac.ContainerBuilder builder) { }
    }
    public class SpecifyAutofacConfigScanner : Specify.Configuration.Scanners.ConfigScanner
    {
        public SpecifyAutofacConfigScanner(Specify.Mocks.IFileSystem fileSystem) { }
        public SpecifyAutofacConfigScanner() { }
        protected override System.Type DefaultBootstrapperType { get; }
    }
}

Because this is the first time the test has been run, the file will be opened in your diff tool so that you can see any changes that have occurred. To make the test pass, just rename "received" to "approved." Now, if the API ever changes, a different API string will be generated in the "received" file and the test will fail, opening your diff tool to show you the changes and allow you to approve them (or change the API back if the change was unintentional). This diagram shows the diff in the Beyond Compare tool.

PublicApiGenerator diff in Beyond Compare

Summary

Public Api Generator and an approval test framework are a great combination for determining if the public API of your package have changed, allowing you to approve the change if it was intentional, and warning you to revert a change if it was accidental.

About Michael Whelan

Michael Whelan is a Technical Lead with over 20 years’ experience in building (and testing!) applications on the Microsoft stack. He is passionate about applying agile development practices, such as BDD and continuous delivery, to agile processes. These days his primary focus is ASP.Net MVC Core and Azure. He contributes to a number of open source frameworks through TestStack.

comments powered by Disqus
Google

Google