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?
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[].
There are two ways you can solve this problem.
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;
}
getenv with an arrayAlternatively, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With