Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test over Autofac Module to reach 100% code coverage

We have a core library which make complex calculations and we consider it critical and we want to have a code coverage of 100% on that library. We now have 96% which is great, but we cant get the 100% because of this class:

public class IoCModule : Autofac.Module
{
    protected override void Load(Autofac.ContainerBuilder builder)
    {
        builder.RegisterType<SomeMathServiceA>().As<ISomeMathServiceA>();
        builder.RegisterType<SomeMathServiceB>().As<ISomeMathServiceB>(); 

        //... more registrations
    }
 }

I dont know how to test it, or if we really need to test it.

I have tried a unit test that take this module and create an IContainer and resolves every register dependency but some services access DB and config files which are very complex to mock in this context.

Done!!!! enter image description here

like image 376
Omar Amalfi Avatar asked Mar 08 '16 08:03

Omar Amalfi


People also ask

What is a reasonable code coverage for unit tests?

With that being said it is generally accepted that 80% coverage is a good goal to aim for. Trying to reach a higher coverage might turn out to be costly, while not necessary producing enough benefit. The first time you run your coverage tool you might find that you have a fairly low percentage of coverage.


2 Answers

Disclaimer: class-level unit test not deemed sensible

(by the author)

I guess by unit-testing you mean "class level unit tests" where the unit is a class. If you want to test the IoCModule you should employ component/library level testing where you test the whole library whether it's working correctly. This (should) include the IoCModule - and all the other stuff in the library. It's usually not feasible to reach 100% branch coverage using tests on this level, but the combination of tests on this level + class level unit tests make for a very good test reliability. I'd also say it's better to reach 80% combined coverage than to only have class-level unit tests. While every class itself can work exactly according to the test, the whole may not work as intended. That's why you should perform component-level tests.

How to verify whether a type is registered:

Now if you're still adamant about performing the tests, look no further, you can do it like that:

public class MyModuleTest
{
    private IContainer container;

    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
        var containerBuilder = new ContainerBuilder();

        // register module to test
        containerBuilder.RegisterModule<MyModule>(); 

        // don't start startable components - 
        // we don't need them to start for the unit test
        this.container = containerBuilder.Build(
            ContainerBuildOptions.IgnoreStartableComponents);
    }

    [TestCaseSource(typeof(TypesExpectedToBeRegisteredTestCaseSource))]
    public void ShouldHaveRegistered(Type type)
    {
        this.container.IsRegistered(type).Should().BeTrue();
    }

    [TestFixtureTearDown]
    public void TestFixtureTearDown()
    {
        this.container.Dispose();
    }

    private class TypesExpectedToBeRegisteredTestCaseSource : IEnumerable<object[]>
    {
        private IEnumerable<Type> Types()
        {
            // whatever types you're registering..
            yield return typeof(string);
            yield return typeof(int);
            yield return typeof(float);
        }

        public IEnumerator<object[]> GetEnumerator()
        {
            return this.Types()
                .Select(type => new object[] { type })
                .GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

This gives test output like: enter image description here

so each type is reported separately.

Wow that was easy - so what's the problem again?

Now in the above example you can see that the test for single (=float) is passing. Now look at the module:

public class MyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<float>();
    }
}

when we actually try to resolve float by:

container.Resolve<float>();

this is what happens:

Autofac.Core.DependencyResolutionException : No constructors on type 'System.Single' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'.

Of course we could just adapt the test to perform the Resolve(Type t) instead of using IsRegistered(Type t) - however there's plenty of other ways to make the test pass - but the implementation fail. For example:

  • binding a type to use builder.RegisterInstance<IFoo>(null)
  • changing lifetime/scopes so it doesn't work properly anymore.
like image 143
BatteryBackupUnit Avatar answered Oct 11 '22 20:10

BatteryBackupUnit


I finally found a way to test it. The autofac Module has a method Configure that register the components. This is how I did it:

public class CheckRegistrations
{
    [Test]
    public void Should_Have_Register_Types()
    {
        //Arrange
        var typesToCheck = new List<Type>
        {
            typeof (ISomeMathServiceA),
            typeof (ISomeMathServiceB)
        };

        //Act
        var typesRegistered = this.GetTypesRegisteredInModule(new IoCModule());

        //Arrange
        Assert.AreEqual(typesToCheck.Count, typesRegistered.Count());

        foreach (var typeToCheck in typesToCheck)
        {
            Assert.IsTrue(typesRegistered.Any(x => x == typeToCheck), typeToCheck.Name + " was not found in module");
        }
    }

    private IEnumerable<Type> GetTypesRegisteredInModule(Module module)
    {
        IComponentRegistry componentRegistry = new ComponentRegistry();

        module.Configure(componentRegistry);

        var typesRegistered =
            componentRegistry.Registrations.SelectMany(x => x.Services)
                .Cast<TypedService>()
                .Select(x => x.ServiceType);

        return typesRegistered;
    }
}
like image 27
Omar Amalfi Avatar answered Oct 11 '22 18:10

Omar Amalfi