嵌套在测试方法中的方法字段上的 NullPointerException

NullPointerException on a field of method nested in tested method

我正在尝试使用 Junit5 和 mockito 编写单元测试,但是当被测试的方法调用嵌套在自身中的另一个方法时,我得到了 NullPointerException。在调试时,我在该方法参数上得到“找不到局部变量”。 这是代码:

测试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("testemail@test.com");
        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("testemail@test.com", testDeck.getOwnerEmail());
        assertEquals(AccessLevel.PUBLIC.getAccessLevel(), testDeck.getAccessLevel());
    }
}

已测试的服务方式:

    @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()
            );
        }
    }

每次它进入 validatePermissionByDeckAccessLevel(Deck deck) 方法时,它都会在 deck 字段上抛出 NPE,即使我确定(我仔细检查过)我正在传递非空值。在尝试调试时,我收到 screen.

上显示的消息

validatePermissionByDeckAccessLevel(Deck deck) 所做的只是检查角色或电子邮件(不过应该无关紧要,因为它已经过测试并且可以正常工作)。

你的 deck 没有问题,你已经用 AccessLevelOwner 存根甲板,而所有者已经有 email.

对此:

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

应该没有关系,你可能只是在变量还不存在的地方放了一个错误的断点。

问题应该来自以下行之一:

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

除了上述主要内容外,您还可以在代码中进行改进:

  • 您已经使用了 @InjectMocks,所以这一行是多余的:deckService = new DeckServiceImpl(deckRepository, userService);
  • 你用了实现,虽然没有问题,但是还是用接口比较好:private UserServiceImpl userService;
  • 您应该通过 api 进行测试,而不是执行:private DeckServiceImpl deckService;
  • DeckDto testDeck = null; 这行是不必要的,你可以这样做: DeckDto testDeck = deckService.findById(1L);
  • 避免使用 anyLong() - 你应该知道你要测试什么,所以你应该准确地测试你想测试的行为。
  • 你调用了两次SecurityContextHolder.getContext().getAuthentication(),最好为其声明一个变量,然后使用getAuthoritiesgetName
  • 这一行:(使用List而不是ArrayList声明变量)
ArrayList<? extends GrantedAuthority> authorities = new ArrayList<>(SecurityContextHolder.getContext().getAuthentication().getAuthorities());

可改为:

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