Tuesday, 29 August 2017

Why does my code throw a null pointer exception? - common reason #1 Redeclaration


TLDR; check that you haven’t redeclared a field as a variable in a setup method

FAQ - why does my code throw a null pointer exception - common reason #1 Redeclaration

  • Using @BeforeClass or @Before can setup data for use in tests
  • Any ‘variables’ we instantiate need to be ‘fields’ rather than variables
  • We want to instantiate them in the setup method rather than redeclare them

Example of the Problem

e.g.

I know I will use an Adder in my test so I create it as a field:


public class WhyCodeThrowsNullPointerExceptionTest {

    Adder adder;

I don’t want to re-instantiate it each time so I make an @BeforeClass method to instantiate it:


@BeforeClass
public static void setupAdder(){
    Adder adder = new Adder();
}

I just made a Semantic coding error. This won’t be caught by a compiler, but it will cause my @Test to fail with a Null Pointer Exception.


@Test
public void canAddTwoPlusTwo(){

    Assert.assertEquals(4,adder.add(2,2));
}

The above test will fail with a NullPointerException


java.lang.NullPointerException
    at com.javafortesters.faq.nullpointerexception.
WhyCodeThrowsNullPointerExceptionTest.canAddTwoPlusTwo
(WhyCodeThrowsNullPointerExceptionTest.java:29)
...

What Went Wrong?


In the setup method I really wanted to assign a value to the field, instead I created an new variable with the same name.

Adder adder = new Adder();

I really wanted:

adder = new Adder();

and to support that, the field really needs to be declared as static

static Adder adder;

How to avoid?

IDE Syntax Highlighting


If I was editing this in an IDE, then I would see that the adder in the following line is never used :


Adder adder = new Adder();

It will be coloured grey or some other IDE indication.




static methods need static fields


In order for the field to be used in the static method it needed to be static.

If I had used it in the setup method:


Adder adder;

@BeforeClass
public static void setupAdder(){
    adder = new Adder();
}

Then I would have seen a syntax error in the code because:
Non-static field 'adder' cannot be referenced from a static context

Refactor to fields


Had I written the @Test code first then I would have started with:


@Test
public void canAddTwoPlusTwo(){
    Adder adder = new Adder();
    Assert.assertEquals(4,adder.add(2,2));
}

Then, when I created a second @Test I would have seen duplication, and I might have chosen to remove that duplication by creating adder as a field, and I could have done that with an automated refactoring of Extract to field where initialized as ‘field declaration’:


Which might have generated the following code:


private final Adder adder = new Adder();

  • this removes the need for an @BeforeClass or @Before construct entirely

I could have refactored as initialized in constructor:


private final Adder adder;

public WhyCodeThrowsNullPointerExceptionTest() {
    adder = new Adder();
}

  • this removes the need for an @BeforeClass or @Before construct entirely

I might have looked at the above code and decided that I needed an @BeforeClass or @Before construct to make the code readable, in which case I could have added the method but I don’t think I would have declared a new variable because I have a working code example that I’m moving.

In General

  • Try to write one test at a time so that if you have a problem it is easier to identify where the problem is
  • Try to write working isolated tests and then refactor to a more general solution when you need it - that way, you know it was working, so you just have to work backwards to find out what went wrong
  • Try to use automated IDE refactoring rather than move code around manually
  • Use the IDE syntax highlighting to help spot any issues

If you want to experiment with code that recreates this problem then have a look at
WhyCodeThrowsNullPointerExceptionTest.java in javaScratchpad






Bonus SlideShare Version




Bonus YouTube Version





No comments:

Post a Comment