12

I have a method like this one:

 public void foo(@Nonnull String value) {...}

I would like to write a unit test to make sure foo() throws an NPE when value is null but I can't since the compiler refuses to compile the unit test when static null pointer flow analysis is enabled in IDE.

How do I make this test compile (in Eclipse with "Enable annotation-based null analysis" enabled):

@Test(expected = NullPointerException.class)
public void test() {
     T inst = ...
     inst.foo(null);
}

Note: In theory the static null pointer of the compiler should prevent cases like that. But there is nothing stopping someone from writing another module with the static flow analysis turned off and calling the method with null.

Common case: Big messy old project without flow analysis. I start with annotating some utility module. In that case, I'll have existing or new unit tests which check how the code behaves for all the modules which don't use flow analysis yet.

My guess is that I have to move those tests into an unchecked module and move them around as I spread flow analysis. That would work and fit well into the philosophy but it would be a lot of manual work.

To put it another way: I can't easily write a test which says "success when code doesn't compile" (I'd have to put code pieces into files, invoke the compiler from unit tests, check the output for errors ... not pretty). So how can I test easily that the code fails as it should when callers ignore @Nonnull?

GhostCat
  • 137,827
  • 25
  • 176
  • 248
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Is it `@Nonnull` or `@NotNull`? – Adam Arold Aug 15 '17 at 12:16
  • What happens if you pass `null` variable? `String s = null; inst.foo(s);` – StanislavL Aug 15 '17 at 12:23
  • 3
    It seems to me like you're trying to test not your class, but the annotation. – daniu Aug 15 '17 at 12:25
  • 1
    Seems specific to intelliJ, but this might help https://stackoverflow.com/a/40847858/1746118 – Naman Aug 15 '17 at 12:30
  • @GhostCat well I can kind of see the point of the test, and it's probably mostly a philosophical issue. But really the case here is that part of the contract is "the method works if null is not passed", and it does not make sense to test the case where the contract is broken. Or, the contract contains "if you pass null, a NullPointerException is thrown", in which case an annotation is the wrong place to implement that - after all, it's the class' responsibility to fulfill the contract, and the Annotation implementation is out of the class' control. – daniu Aug 15 '17 at 12:41
  • @AdamArold That doesn't really matter. The IDEs that I know of can be configured to use different annotations for static null analysis. – Aaron Digulla Aug 15 '17 at 14:35
  • @GhostCat I could add some details like the exact annotation which I'm using but there are many other which are supposed to achieve the same effect, so I'm leaving that out on purpose. The question works for all of them. The code doesn't compile which implies I have configured the IDE compiler to handle the annotation properly. How the instance is created is irrelevant. – Aaron Digulla Aug 15 '17 at 14:46
  • @GhostCat Lastly, I know that the annotation doesn't change behavior, it just causes the compiler to refuse to compile the test. That what I want in production code but I want to disable it for a few tests where I want to make sure that the code breaks properly. I think code which says `@Nonnull`, but doesn't throw NPE, smells. – Aaron Digulla Aug 15 '17 at 14:48
  • @StanislavL The `s` is flagged as error "Variable can only be null at this point". – Aaron Digulla Aug 15 '17 at 14:50
  • @AaronDigulla My answer is probably more of a comment ...well: I can't repro. Works fine in my eclipse. – GhostCat Aug 15 '17 at 14:53
  • You could use more complicated case `s = Boolean.TRUE ? null : ""` or ` s = "TRUE'.toLowerCase().equals("true") ? null : "" ` to hide var init logic from compiler but still have null variable. – StanislavL Aug 15 '17 at 14:55
  • Is `@Nonnull` the same as `javax.annotation.Nonnull`? – kevinarpe Sep 15 '20 at 15:46

5 Answers5

8

Hiding null within a method does the trick:

public void foo(@NonNull String bar) {
    Objects.requireNonNull(bar);
}

/** Trick the Java flow analysis to allow passing <code>null</code>
 *  for @Nonnull parameters. 
 */
@SuppressWarnings("null")
public static <T> T giveNull() {
    return null;
}

@Test(expected = NullPointerException.class)
public void testFoo() {
    foo(giveNull());
}

The above compiles fine (and yes, double-checked - when using foo(null) my IDE gives me a compile error - so "null checking" is enabled).

In contrast to the solution given via comments, the above has the nice side effect to work for any kind of parameter type (but might probably require Java8 to get the type inference correct always).

And yes, the test passes (as written above), and fails when commenting out the Objects.requireNonNull() line.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
GhostCat
  • 137,827
  • 25
  • 176
  • 248
2

Why not just use plain old reflection?

try {
    YourClass.getMethod("foo", String.class).invoke(someInstance, null);
    fail("Expected InvocationException with nested NPE");
} catch(InvocationException e) {
    if (e.getCause() instanceof NullPointerException) {
        return; // success
    }
    throw e; // let the test fail
}

Note that this can break unexpectedly when refactoring (you rename the method, change the order of method parameters, move method to new type).

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 1
    Because reflection breaks easily? – GhostCat Aug 16 '17 at 07:59
  • 1
    But beyond that: one could go one step further - it *might* be possible to automate more of that. Meaning: if "instance creation" follows some common patterns, one could scan the class path for all custom classes, and all their public methods for the annotation. And then you not only invoke that method by reflection , but you also create the instance to invoke on. – GhostCat Aug 16 '17 at 08:42
  • Well it's a test. Tests break, then you fix them. Obviously I wouldn't suggest this approach for production code (at least not unless there is a test to validate it) – Sean Patrick Floyd Aug 16 '17 at 16:43
2

Using assertThrows from Jupiter assertions I was able to test this:

public MethodName(@NonNull final param1 dao) {....

assertThrows(IllegalArgumentException.class, () -> new MethodName(null));
Sam Gruse
  • 488
  • 1
  • 5
  • 11
  • 1
    When null checking is enabled in the compiler, then this should not compile... – Aaron Digulla Mar 04 '21 at 08:37
  • This will only compile for me if I call the method in the assertThrows() method. If I call the method normally it will not compile. – Sam Gruse Mar 05 '21 at 23:28
  • This looks like a compiler bug for me: It should not treat `assertThrows()` in a special way. Can you compile `Supplier foo = () -> new MethodName(null);`? How about `Executable foo = () -> new MethodName(null);`? – Aaron Digulla Mar 11 '21 at 15:43
0

Here design by contract comes to picture. You can not provide null value parameter to a method annotated with notNull argument.

LONGHORN007
  • 526
  • 9
  • 24
  • Hmm. What if you flip a random coin, and in 10% of all cases you assign null to a field ... which is then later used as parameter to that method? Do you think any compile time checking will tell you about that? – GhostCat Aug 15 '17 at 13:16
  • 2
    And beyond that: as your input reads like now, it is much more of a comment. An **answer** should answer the question. Or, when giving a non-answer: make it very very clear why the question doesn't make sense. – GhostCat Aug 15 '17 at 13:17
  • If you expect that the method argument(Not parameter) can be null, then why do u write the API Contract as notNull. – LONGHORN007 Aug 16 '17 at 07:05
  • The point is: we both missed the point of the question initially. The OP wants to *verify* that any method that uses @NonNull on arguments checks these arguments. – GhostCat Aug 16 '17 at 08:00
  • We can then write a client java project to call the said method and pass null value to it. The client hava project can be treated as unit test code – LONGHORN007 Aug 16 '17 at 08:49
  • And the question is asking *how to do write such a piece of client code*. And your answer doesn't address that at **all**. – GhostCat Aug 16 '17 at 08:50
  • My understanding here is that we are trying to test not our class method, but the annotation. Then why we will do it . – LONGHORN007 Aug 16 '17 at 09:08
  • Again: read the question. He doesnt test the annotation. He wants to test the **company internal contract** that any method that uses such annotations also **checks** the parameters for being not null. – GhostCat Aug 16 '17 at 09:12
  • We can write the @Test public void test(){ assertNotNull(methodargument)); inst.foo(methodargument); } } – LONGHORN007 Aug 16 '17 at 09:16
0

You can use a field which you initialize and then set to null in a set up method:

private String nullValue = ""; // set to null in clearNullValue()
@Before
public void clearNullValue() {
    nullValue = null;
}

@Test(expected = NullPointerException.class)
public void test() {
     T inst = ...
     inst.foo(nullValue);
}

As in GhostCat's answer, the compiler is unable to know whether and when clearNullValue() is called and has to assume that the field is not null.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820