mBo
mBo

Reputation: 155

Mockito mock annotation multiple objects

I'm trying to understand mockito but I'm getting stuck, im trying to mock multiple objects using the @Mock annotation, but it will not mock them. It will only mock the first object (mockBoard). If I mock the BoardFactory myself using mock(Boardfactory.class) it will work. But i dont understand why it wont work in the @Mock?

@ExtendWith(MockitoExtension.class)
class MapParserTest {

//mocks just fine
@Mock private Board mockBoard;

//wont mock both factories, sets them to null
@Mock private BoardFactory mockBoardFactory;
@Mock private LevelFactory mockLevelFactory;

//this will work
//private BoardFactory mockBoardFactory = mock(BoardFactory.class);

private MapParser mapParser = new MapParser(mockLevelFactory, mockBoardFactory);
private List<Ghost> ghosts = new ArrayList<>();
private List<Square> startPositions = new ArrayList<>();

@Test
void testParseCharMatrix() {
    //Arrange
    char[][] mapMatrix = new char[1][];
    mapMatrix[0] = new char[]{'#'};

    //nullPointer exception thrown here
    when(mockBoardFactory.createBoard(any(Square[][].class))).thenReturn(mockBoard);

    //Act
    mapParser.parseMap(mapMatrix);

    //Assert
    verify(mockLevelFactory).createLevel(mockBoard, ghosts, startPositions);
}}

Upvotes: 0

Views: 4475

Answers (2)

Florian Schaetz
Florian Schaetz

Reputation: 10662

private MapParser mapParser = new MapParser(mockLevelFactory, mockBoardFactory);

Here, mockLevelFactory and mockBoardFactory will always be null, no matter if JUnit4, 5, @Rule or @ExtendWith. Why? Because that line gets called as soon as your test class is instantiated, long before Mockito had a chance to run and put mocks into your annotated variables:

  1. Test-Class gets instantiated
  2. The following lines get executed:

    private MapParser mapParser = new MapParser(mockLevelFactory, mockBoardFactory);
    private List<Ghost> ghosts = new ArrayList<>();
    private List<Square> startPositions = new ArrayList<>();

  3. Mockito creates the following mocks:

    @Mock private Board mockBoard;
    @Mock private BoardFactory mockBoardFactory;
    @Mock private LevelFactory mockLevelFactory;

  4. (some time later) The actual test method is called

This is why mockLevelFactory and mockBoardFactory are always null in your MapParser. They are not null in your test, but they are null at the moment when you create your MapParser.

The solution? Create the instance of your MapParser later, for example in a @BeforeEach method...

@BeforeEach
public void beforeEach() {
   // This method gets called AFTER Mockito created the mocks
   mapParser = new MapParser(mockLevelFactory, mockBoardFactory);
}

...or also simply at the start of your test method (but one could consider that bad style).

Upvotes: 2

kapex
kapex

Reputation: 30019

Are you sure the first field is actually initialized? Unless you run the test with MockitoJUnitRunner, you need to initialize the annotated fields of the test class manually, for example like this:

@BeforeEach 
public void initMocks() {
    MockitoAnnotations.initMocks(this);
}

As a side note, even if the mocks are created correctly, then I'm note sure if the field mapParser will be initialized correctly. It's likely that mocks are injected after the test class has been initialized, so you probably can't use mocked field in the constructor or in field initializers. You probably need to initialize that field in a @BeforeEach method, like you had in the beginning.

Upvotes: 0

Related Questions