mat_boy
mat_boy

Reputation: 13666

JaCoCo and missed coverage of private default constructor

I'd like to see an example to prevent JaCoCo to report private empty constructors as non-covered code in a Java class.

In the maven plugin configuration I have

   <rule>
     <element>CLASS</element>
       <excludes>
         <exclude>JAVAC.SYNTHCLASS</exclude>
         <exclude>JAVAC.SYNTHMETH</exclude>
       </excludes>
     </element>
   </rule>

Isn't there something similar for the constructor?

Upvotes: 14

Views: 17353

Answers (7)

Clairton Luz
Clairton Luz

Reputation: 2354

If you are using Lombok, the easiest way is to annotate the constructor with @Generated, so JaCoCo will ignore this constructor.

import lombok.Generated;

public class MyClass {
   @Generated
   private MyClass(){}
}

Upvotes: 0

Piotr Polak
Piotr Polak

Reputation: 497

As 0.8.0 is not yet released, I created a hamcrest matcher that checks whether a class is an utility class and additionally calls the private constructor using reflection (for code coverage purpose only).

https://github.com/piotrpolak/android-http-server/blob/master/http/src/test/java/ro/polak/http/utilities/IOUtilitiesTest.java

package ro.polak.http.utilities;

import org.junit.Test;


import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static ro.polak.http.ExtraMarchers.utilityClass;

public class IOUtilitiesTest {

    @Test
    public void shouldNotBeInstantiable() {
        assertThat(IOUtilities.class, is(utilityClass()));
    }
}

https://github.com/piotrpolak/android-http-server/blob/master/http/src/test/java/ro/polak/http/ExtraMarchers.java

package ro.polak.http;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ExtraMarchers {

    private static final UtilClassMatcher utilClassMatcher = new UtilClassMatcher();

    public static Matcher<? super Class<?>> utilityClass() {
        return utilClassMatcher;
    }

    private static class UtilClassMatcher extends TypeSafeMatcher<Class<?>> {
        @Override
        protected boolean matchesSafely(Class<?> clazz) {
            boolean isUtilityClass = false;
            try {
                isUtilityClass = isUtilityClass(clazz);
            } catch (ClassNotFoundException | InstantiationException e) {
                // Swallowed
            }

            // This code will attempt to call empty constructor to generate code coverage
            if (isUtilityClass) {
                callPrivateConstructor(clazz);
            }

            return isUtilityClass;
        }

        @Override
        protected void describeMismatchSafely(Class<?> clazz, Description mismatchDescription) {
            if (clazz == null) {
                super.describeMismatch(clazz, mismatchDescription);
            } else {
                mismatchDescription.appendText("The class " + clazz.getCanonicalName() + " is not an utility class.");

                boolean isNonUtilityClass = true;
                try {
                    isNonUtilityClass = !isUtilityClass(clazz);
                } catch (ClassNotFoundException e) {
                    mismatchDescription.appendText(" The class is not found. " + e);
                } catch (InstantiationException e) {
                    mismatchDescription.appendText(" The class can not be instantiated. " + e);
                }

                if (isNonUtilityClass) {
                    mismatchDescription.appendText(" The class should not be instantiable.");
                }
            }
        }

        @Override
        public void describeTo(Description description) {

        }

        private void callPrivateConstructor(Class clazz) {
            try {
                Constructor<?> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);
                constructor.newInstance();
            } catch (NoSuchMethodException | IllegalAccessException |
                    InstantiationException | InvocationTargetException e) {
                // Swallowed
            }
        }

        private boolean isUtilityClass(Class clazz) throws ClassNotFoundException, InstantiationException {
            boolean hasPrivateConstructor = false;
            try {
                clazz.newInstance();
            } catch (IllegalAccessException e) {
                hasPrivateConstructor = true;
            }
            return hasPrivateConstructor;
        }
    }
}

Upvotes: -1

hageldave
hageldave

Reputation: 63

This is not solving the essential problem that empty private constructors should not need coverage, but to actually make JaCoCo report coverage on an empty private constructor you need to call it. How do you do that? You call it in the static initialization block.

public class MyClass {
   static {
      new MyClass();
   }
   private MyClass(){}
}

EDIT: Turned out that there is no guarantee on the static initialization block to be executed. Thus we are limited to using methods as this one:

static <T> void callPrivateConstructorIfPresent(Class<T> clazz){
        try{
            Constructor<T> noArgsConstructor = clazz.getDeclaredConstructor();
            if(!noArgsConstructor.isAccessible()){
                noArgsConstructor.setAccessible(true);
                try {
                    noArgsConstructor.newInstance();
                } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) 
                {
                    e.printStackTrace();
                }
                noArgsConstructor.setAccessible(false);
            }
        } catch(NoSuchMethodException e){}
    }

Upvotes: -1

Jesus Zavarce
Jesus Zavarce

Reputation: 1759

This is not supported. The official documentation says:

Filters for Code where Test Execution is Questionable or Impossible by Design

  • Private, empty default constructors - assuming no calls to it
  • Plain getters and setters
  • Blocks that throw AssertionErrors - Entire block should be ignored if a condition (if !assertion throw new AssertionError)

see also : https://github.com/jacoco/jacoco/issues/298

Update: This was fixed in https://github.com/jacoco/jacoco/pull/529 and should be in 0.8.0.

Upvotes: 10

Snehal Patel
Snehal Patel

Reputation: 1330

As per official documentation, it's going to be released with 0.8.0

Filters for Code where Test Execution is Questionable or Impossible by Design

Private empty constructors that do not have arguments - Done

You can find details here.

Upvotes: 0

fdaugan
fdaugan

Reputation: 798

For this use case, reflection is perfectly acceptable, there are few and well known classes. The bellow code could be used with an automatic class detection based on the name. For sample ".*Factory" classes with additional asserts.

@Test
public void testCoverage()
    throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
    coverageSingleton(MySingleton1.class);
    coverageSingleton(MySingleton2.class);
}

private <S> void coverageSingleton(Class<S> singletonClass)
    throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
    final Constructor<S> constructor = singletonClass.getDeclaredConstructor();
    constructor.setAccessible(true);
    constructor.newInstance();
}

Upvotes: 1

Łukasz
Łukasz

Reputation: 216

There is no way to turn that option off. If you desperately need to meet some quality gate related to coverage you can always use a workaround and invoke these private constructors via reflection.

Upvotes: 1

Related Questions