Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@InjectMocks, the constructor or the initialization block threw an exception

When I use @InjectMocks, an exception was occurred. My code is shown below:

class A {
    private X x;
    private Y y;
    public A(String ip, int port) {
       this(someMethodCall(ip, port)); //
    }

    private A(X x) {
        this.x = x;
        this.y = new Y();
    }
}
UT:
public class ATest() {
    @InjectMocks A a;
    @Mock X x;
    @Mock Y y;
    @Test ...
}

it will throw an NPE, can someone help me?

org.mockito.exceptions.base.MockitoException: Cannot instantiate @InjectMocks field named 'channel' of type 'class Juinit3.Channel'. You haven't provided the instance at field declaration so I tried to construct the instance. However, the constructor or the initialization block threw an exception: null.

like image 686
dingrui Avatar asked Nov 24 '17 02:11

dingrui


3 Answers

The problem is with your @InjectMocks field. Since you did not initialize it directly like this:

@InjectMocks A a = new A("localhost", 80);

mockito will try to do constructor initialization. In this case it will choose the biggest constructor. In your case it's public A(String ip, int port). If no mock fields as provided that will match the constructor arguments, the mockito will pass nulls as a values for choosen constructor. So in this case the instance is initialized as new A(null, null). In this case you will get NPE, since the second parameter of your constructor is int, and when the null will is going to be unboxed to int the NPE will be thrown.

like image 63
Sergii Bishyr Avatar answered Sep 18 '22 12:09

Sergii Bishyr


What this exeception is telling you...

You haven't provided the instance at field declaration

In other words, you did not write...

@InjectMocks 
A a = new A("foobar", 123);

This would be completely acceptable and will probably solve your problem. Please remember that mocks will NOT be initialized at that point, so it's fine if you really need an example String and int there, but not if you need to put mocks there. In other words, if you had a constructor that took an X and you would write new A(x) here, x would be null, since the @Mock annotation would not have been processed yet.

so I tried to construct the instance

Because there was no instance (because you didn't provide one) it tried to create one, but...

However, the constructor or the initialization block threw an exception: null

So, your constructor throws null. Seems like your someMethodCall relies on the arguments (port, most likely) given not to be null, but since they are String and int, Mockito has no idea what values to use there. Since port is a primitive type and Mockito does not handle those specifically, the problem is probably there - Mockito will try to put null there, which will throw an exception.

If your constructor matched X and Y, for example, Mockito would probably try to put the mocks there, but it doesn't. The constructor wants String and int and there are no mocks for them, so Mockito can only use default values and those are null, which is a problem in case of port (because of int).

So, what's the solution?

1) Either make your constructor null-safe, allowing to give a null-port there (and make sure that the ip string is also handled in a null-safe way).

2) Use the thing you didn't use:

@InjectMocks 
A a = new A("foobar", 123);

In any case, it is not required to have all the depedencies in the constructor, Mockito will do fine injecting them into fields directly. So adding another constructor for X and Y is not a real solution. Of course, generally, constructor injection is preferable over field injection, but that's another topic.

As for your question about which constructor: The documentation says this...

the biggest constructor is chosen, then arguments are resolved with mocks declared in the test only

Edit: Seems that Mockito does not know how to handle primitive fields in constructors, what a shame.

like image 22
Florian Schaetz Avatar answered Sep 19 '22 12:09

Florian Schaetz


1) Either create a public non-args constructor or create public A(X x, Y y) constructor.

2) Make sure you use

@RunWith(MockitoJUnitRunner.class)
public class ATest() {

or

@Before
public void init(){
  MockitoAnnotations.initMocks(this);
}
like image 36
Maciej Kowalski Avatar answered Sep 19 '22 12:09

Maciej Kowalski