Nulls and varargs
I needed to work out how you can invoke methods that have varargs
when you don't actually have anything to send for that parameter. Below are the lessons I have learned and below that is the JUnit test that proves it. I am using JDK 7.
- When a method has a
varargs
argument, you can completely ignore it and not put anything at all in that place. - A
varargs
parameter always has a type, and you can send a single value of that type - or an array of that type. - The array can be empty - but your code should be able to deal with that.
- The parameter can be null, although Eclipse wants you cast the null - and your code must be prepared to deal with nulls (or you will throw a
NullPointerException
). - When using
mockit.Deencapsulation
to invoke a method that acceptsvarargs
, you cannot ignore the parameter; it has to have something there or JMockit will throw anIllegalArgumentException
. mockit.Deencapsulation
throws anIllegalArgumentException
if you send null (cast or not) in place of thevarargs
becausenull
isn't enough information for JMockit to work out what type of argument it is.- You can tell
mockit.Deencapsulation
to send null in place of thevarargs
by using aX[].class
as the parameter (where X is the type expected byvarargs
) - and your code must be prepared to deal with nulls (or you will throw aNullPointerException
). See http://bit.ly/ZMYZuR.
Here is the test.
import static org.junit.Assert.assertNotNull; import java.nio.file.Paths; import mockit.Deencapsulation; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Test var args. What calling a method with varargs, you can totally leave out * the vararg parameter, but you cannot use null - unless you cast it. But you * also can't use a null (even a casted one) when you are using JMockit * {@link Deencapsulation#invoke(Class, String, Object...)}. * * @author RobertMarkBram */ @RunWith(JUnit4.class) public final class TestVarArgs { /** * A method with var args. * * @param first * first path * @param more * the rest - may be null */ public void readFiles(final String first, final String... more) { if (more == null || more.length == 0 || more[0] == null) { assertNotNull(Paths.get(first).toString()); } else { assertNotNull(Paths.get(first, more).toString()); } } /** * Calling {@link #readFiles(String, String...)} with just one arg should * work. Completely ignoring the vararg. */ @Test public void callMethodWithOneArg() { readFiles("C:/Temp"); } /** * Calling {@link #readFiles(String, String...)} with just one arg and an * empty string should work. */ @Test public void callMethodWithOneArgAndEmptyString() { readFiles("C:/Temp", ""); } /** * Calling {@link #readFiles(String, String...)} with just one arg and an * empty array should work. */ @Test public void callMethodWithOneArgAndEmptyArray() { String[] empty = {}; readFiles("C:/Temp", empty); } /** * Calling {@link #readFiles(String, String...)} with just one arg and an * array with a blank string should work. */ @Test public void callMethodWithOneArgAndArrayWithEmptyString() { String[] empty = { "" }; readFiles("C:/Temp", empty); } /** * Calling {@link #readFiles(String, String...)} with just one arg and a null * works, but you have to do a null check on the varargs in case it is null. * Plus, Eclipse gives you a warning for not casting null as vararg. */ @Test @SuppressWarnings("all") public void callMethodWithOneArgAndNull() { readFiles("C:/Temp", null); } /** * Calling {@link #readFiles(String, String...)} with just one arg and a null * cast to the expected type works, but you have to do a null check on the * varargs in case it is null. */ @Test public void callMethodWithOneArgAndStringCastNull() { readFiles("C:/Temp", (String) null); } /** * Calling {@link #readFiles(String, String...)} with just one arg and a null * cast to the expected type works, but you have to do a null check on the * varargs in case it is null. */ @Test public void callMethodWithOneArgAndCastNull() { readFiles("C:/Temp", (String[]) null); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg fails with an {@link IllegalArgumentException} because JMockit * requires a valid varargs argument, i.e., not null. * * @see http://bit.ly/ZMYZuR */ @Test(expected = IllegalArgumentException.class) public void callMethodWithOneArgJmockit() { Deencapsulation.invoke(this, "readFiles", "C:/Temp"); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg fails with an {@link IllegalArgumentException} because JMockit * requires a valid varargs argument, i.e., not a single element of the * array. * * @see http://bit.ly/ZMYZuR */ @Test(expected = IllegalArgumentException.class) public void callMethodWithOneArgAndEmptyStringJmockit() { Deencapsulation.invoke(this, "readFiles", ""); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg and an empty array should work. */ @Test public void callMethodWithOneArgAndEmptyArrayJmockit() { String[] empty = {}; Deencapsulation.invoke(this, "readFiles", "C:/Temp", empty); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg and an array with a blank string should work. */ @Test public void callMethodWithOneArgAndArrayWithEmptyStringJmockit() { String[] empty = { "" }; Deencapsulation.invoke(this, "readFiles", "C:/Temp", empty); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg and a null fails with an {@link IllegalArgumentException} because * JMockit requires a valid varargs argument, i.e., not null. * * @see http://bit.ly/ZMYZuR */ @Test(expected = IllegalArgumentException.class) public void callMethodWithOneArgAndNullJmockit() { Deencapsulation.invoke(this, "readFiles", "C:/Temp", null); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg and a null cast to the expected type fails with an * {@link IllegalArgumentException} because JMockit requires a valid varargs * argument, i.e., not null. * * @see http://bit.ly/ZMYZuR */ @Test(expected = IllegalArgumentException.class) public void callMethodWithOneArgAndCastNullJmockit() { Deencapsulation.invoke(this, "readFiles", "C:/Temp", (String[]) null); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg and a null cast to the expected type fails with an * {@link IllegalArgumentException} because JMockit requires a valid varargs * argument, i.e., not null. * * @see http://bit.ly/ZMYZuR */ @Test(expected = IllegalArgumentException.class) public void callMethodWithOneArgAndStringCastNullJmockit() { Deencapsulation.invoke(this, "readFiles", "C:/Temp", (String) null); } /** * Calling {@link #readFiles(String, String...)} via JMockit with just one * arg and a null cast to the expected type fails with an * {@link IllegalArgumentException} because JMockit requires a valid varargs * argument, i.e., not null. * * @see http://bit.ly/ZMYZuR */ public void callMethodWithOneArgAndNullArrayJmockit() { Deencapsulation.invoke(this, "readFiles", "C:/Temp", String[].class); } }
Further reading.
- Notes on the introduction of varargs in JDK 1.5.
- Java trail: Passing Information to a Method or a Constructor.
- I got a few pointers on what nulls to test thanks to einpoklum's answer to my StackOverflow post: @SupressWarnings for the invocation of the varargs with un-cast null.