Issue
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:
@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).
Solution
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 usegetAuthorities
orgetName
- This line: (use
List
to declare the variable instead ofArrayList
)
ArrayList<? extends GrantedAuthority> authorities = new ArrayList<>(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
can be changed to:
List<? extends GrantedAuthority> authorities = new ArrayList<>(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
Answered By - nnhthuan
Answer Checked By - Pedro (JavaFixing Volunteer)