Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The JUnit Assume.assumeNotNull throws nullpointer instead of skipping test

Tags:

junit

groovy

I have this junit (using JUnit 4.12) test in groovy that should only be executed if getenv != null:

    import org.junit.Assume
    import org.junit.Before
    import org.junit.Ignore
    import org.junit.Test
    ...
    @Test
    public void skipWhenNull() throws Exception {
        def getenv = System.getenv("bogos")
        if( getenv == null) {
            println "Its null!"
        }
        Assume.assumeNotNull(getenv);
        println "Test executing!"
    }

But when I run the test it prints: Its null! and then throws a NullPointer exception (in the line: Assume.assumeNotNull(getenv); ). Is the point of: Assume.assumeNotNull(expr) not that it will skip the test when expr evaluates to null instead of throwing a Nullpointer?

like image 397
u123 Avatar asked Nov 26 '25 08:11

u123


1 Answers

I thought this might be a bug, but I think this is rather a consequence of Groovy's dynamic type system in its default behavior. Assume.assumeNotNull(Object... objects) method uses varargs parameters. It means that in case of passing a non-array element compiler wraps it within an array of expected type.

String getenv = System.getenv("bogos"); 

Assume.assumeNotNull(getenv); // --> Assume.assumeNotNull(new Object[] { getenv });

This is what Java's static compiler does. So in case of getenv == null we end up with:

Assume.assumeNotNull(new Object[] { null });

A non-empty array that contains a single null element. On the other hand, if we specify a variable with an array type and we assign a null value to it, calling the same method will cause NullPointerException, just like in the following example:

String[] array = null;

Assume.assumeNotNull(array); // --> throws NPE

This is at least what happens in Java. Now, why it fails in Groovy unit test? Groovy is a dynamically typed language by default, so it behaves quite differently in this area. It seems like Groovy's type system applies null value to the method type without wrapping it within an array, so in case of passing a null to a method that expects e.g. Object... objects we always get objects == null instead of objects == new Object[] { null }.

I started questioning myself if this is a bug or not. From one perspective I would expect dynamic Groovy to behave in the same way that statically compiled code behaves. But on the other hand in a dynamically typed system, this distinction is acceptable (and maybe even desirable), because dynamic type system infers the type at the runtime. It sees null so it thinks that we intend to assign null value to a variable of type Object[].

Solution

There are two ways you can solve this problem.

1. Enable static compilation

If you don't use Groovy's dynamic and metaprogramming features in your test case you can easily annotate it with @groovy.transform.CompileStatic annotation to generate a bytecode that is more similar to Java's bytecode. For instance, this is what the bytecode of your method in the dynamic Groovy looks like:

@Test
public void skipWhenNull() throws Exception {
    CallSite[] var1 = $getCallSiteArray();
    Object getenv = var1[4].call(System.class, "bogos");
    if (ScriptBytecodeAdapter.compareEqual(getenv, (Object)null)) {
        var1[5].callCurrent(this, "Its null!");
    }

    var1[6].call(Assume.class, getenv);
    var1[7].callCurrent(this, "Test executing!");
}

And here is the same method but annotated with @CompileStatic from the bytecode perspective:

@Test
public void skipWhenNull() throws Exception {
    String getenv = System.getenv("bogos");
    Object var10000;
    if (getenv == null) {
        DefaultGroovyMethods.println(this, "Its null!");
        var10000 = null;
    }

    Assume.assumeNotNull(new Object[]{getenv});
    var10000 = null;
    DefaultGroovyMethods.println(this, "Test executing!");
    var10000 = null;
}

2. Wrap getenv with an array

Alternatively, you can make more explicit call to Assume.assumeNotNull method. If you replace:

Assume.assumeNotNull(getenv);

with:

Assume.assumeNotNull([getenv] as Object[]);

then you will wrap parameter explicitly with Object[] array and you will prevent from passing an array object that is represented by null, but a single element array holding null value instead.

like image 153
Szymon Stepniak Avatar answered Nov 28 '25 01:11

Szymon Stepniak



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!