Home > Software design >  NullPointerException on a field of method nested in tested method
NullPointerException on a field of method nested in tested method

Time:05-05

Im trying to write a unit test with Junit5 and mockito but im getting NullPointerException when tested method calls another method nested in itself. While debugging im getting "cannot find local variable" on that method parameter. Here's the code:

Test class:

@ExtendWith(MockitoExtension.class)
class DeckServiceImplTest {

    @Mock
    private UserServiceImpl userService;
    @Mock
    private DeckRepository deckRepository;
    @InjectMocks
    private DeckServiceImpl deckService;

    @BeforeEach
    void setUp() {
        deckService = new DeckServiceImpl(deckRepository, userService);
    }

    @Test
    @DisplayName("when deck id is provided returned deck should be correct")
    @WithMockUser(username = "user", password = "user", roles = "USER")
    public void whenDeckIdIsProvidedThenRetrievedDeckIsCorrect() {
        //given
        DeckDto testDeck = null;
        User deckBaseEntityOwner = new User();
        deckBaseEntityOwner.setEmail("[email protected]");
        deckBaseEntityOwner.setId(10L);
        deckBaseEntityOwner.setRole(Role.ADMIN);
        Deck deckBaseEntity = new Deck();
        deckBaseEntity.setName("deck name");
        deckBaseEntity.setAccessLevel(AccessLevel.PUBLIC.getAccessLevel());
        deckBaseEntity.setOwner(deckBaseEntityOwner);
        deckBaseEntity.setId(1L);
        //when
        when(deckRepository.findById(anyLong())).thenReturn(of(deckBaseEntity));
        testDeck = deckService.findById(1L);
        //then
        verify(deckRepository).findById(anyLong());
        assertNotNull(testDeck);
        assertEquals("deck name", testDeck.getName());
        assertEquals(10L, testDeck.getOwnerId());
        assertEquals("[email protected]", testDeck.getOwnerEmail());
        assertEquals(AccessLevel.PUBLIC.getAccessLevel(), testDeck.getAccessLevel());
    }
}

Tested service methods:

    @Override
    public DeckDto findById(Long id) throws ElementNotFoundByIdException, PermissionDeniedException {
        Deck deck = deckRepository.findById(id).orElseThrow(() -> new ElementNotFoundByIdException(
                CAN_NOT_FIND_DECK_BY_ID_ERROR_MESSAGE.getMessage()   id,
                CAN_NOT_FIND_DECK_BY_ID_ERROR_CODE.getValue()
        ));
        validatePermissionByDeckAccessLevel(deck);
        return modelMapper.map(deck, DeckDto.class);
    }

    private void validatePermissionByDeckAccessLevel(Deck deck) throws PermissionDeniedException {
        ArrayList<? extends GrantedAuthority> authorities = new ArrayList<>(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
        if (!SecurityContextHolder.getContext().getAuthentication().getName().equals(deck.getOwner().getEmail())
                && !deck.getAccessLevel().equals(AccessLevel.PUBLIC.getAccessLevel())
                && !authorities.get(0).getAuthority().equals(Role.ADMIN.getRole())) {
            throw new PermissionDeniedException(
                    USER_DONT_HAVE_PERMISSIONS_ERROR_MESSAGE.getMessage(),
                    USER_DONT_HAVE_PERMISSIONS_ERROR_CODE.getValue()
            );
        }
    }

Everytime it enters the validatePermissionByDeckAccessLevel(Deck deck) method it throws NPE on deck field even though im sure (i double checked) that im passing non null value. While trying to debug im getting message shown on the screen.

All that validatePermissionByDeckAccessLevel(Deck deck) does is just checks roles or email (shouldn't matter though because it was tested and it works).

CodePudding user response:

Your deck has no problem, you already stub deck with AccessLevel and Owner, and owner already had email.

Regarding this:

While debugging im getting "cannot find local variable" on that method parameter

It should not be related, you probably just put a wrong breakpoint where the variable wasn't there yet.

The problem should be come from one of these lines:

  • SecurityContextHolder.getContext().getAuthentication().getAuthorities()
  • SecurityContextHolder.getContext().getAuthentication().getName()
  • authorities.get(0).getAuthority()

Besides the main things above, there are things you can improve in your code:

  • You already used @InjectMocks, so this line is redundant: deckService = new DeckServiceImpl(deckRepository, userService);
  • You used the implementation, although there is not problem, but it's better to use the interface: private UserServiceImpl userService;
  • You should test through the api, not the implementation: private DeckServiceImpl deckService;
  • DeckDto testDeck = null; this line is unnecessary, you just can do it as: DeckDto testDeck = deckService.findById(1L);
  • Avoid using anyLong() - You should know what you're gonna test, so you should test exactly the behavior you wanna test.
  • You called SecurityContextHolder.getContext().getAuthentication() twice, it's better to declare a variable for it, then use getAuthorities or getName
  • This line: (use List to declare the variable instead of ArrayList)
ArrayList<? extends GrantedAuthority> authorities = new ArrayList<>(SecurityContextHolder.getContext().getAuthentication().getAuthorities());

can be changed to:

List<? extends GrantedAuthority> authorities = new ArrayList<>(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
  • Related