Search code examples
javaspring-bootjunitmapper

spring boot Junit testing:testing service layer and mapper class pass null pointer exception


This is my test service

@RunWith(SpringRunner.class)
class UserServiceTest {
    @InjectMocks
    UserMapper userMapper = mock(UserMapper.class);
    @InjectMocks
    UserServiceImpl userServiceImpl=mock(UserServiceImpl.class);
    @Mock
    UserRepository userRepository = mock(UserRepository.class);
    @Mock
    UserService userService=mock(UserService.class);
    @BeforeEach
    public void setup(){
        MockitoAnnotations.initMocks(this);
        userRepository = Mockito.mock(UserRepository.class);
      
        userServiceImpl = new UserServiceImpl(userRepository);
    }


    @Test
    void add() {
        AddUserRequest addUserRequest = new AddUserRequest("guest","1234567890","guest98787@guest.com","1234567890");

        User user = new User();
        user.setUserId(1);
        user.setUid("12345678");
        user.setName("guest");
        user.setMobileNo("1234567890");
        user.setEmail("guest98787@guest.com");
        user.setPassword("1234567890");
        WalletDetail walletDetail=new WalletDetail();
        walletDetail.setWalletId(1);
        walletDetail.setUid("12345");
        walletDetail.setUser(user);
        walletDetail.setWalletAmount(10.0);
        user.setWallet(walletDetail);

        when(userMapper.addRequestUserToEntity(addUserRequest)).thenReturn(user);
        when(userRepository.save(user)).thenReturn(user);
        when(userServiceImpl.add(addUserRequest)).thenReturn(user);
        User result = userServiceImpl.add(addUserRequest);
       
        verify(userServiceImpl,times(1)).add(addUserRequest);
        assertEquals(user.getUserId(), result.getUserId());
    }

This is my service layer -implemented Mapstuck

@Slf4j
@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private WalletService walletService;

    private UserServiceImpl userServiceImpl;
    

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository=userRepository;
    }
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public User add(AddUserRequest addUserRequest) {
        log.debug("Request received for user registration:  " + addUserRequest);

        User user = createUser(addUserRequest);

        WalletDetail walletDetail = walletService.createDefaultWallet();
        walletDetail.setUser(user);
        user.setWallet(walletDetail);

        log.debug("User saved with data " + user);
        return userRepository.save(user);
    }

    private User createUser(AddUserRequest addUserRequest) {
        User user = userMapper.addRequestUserToEntity(addUserRequest);
        user.setCreatedBy(user.getUid());
        user.setUpdatedBy(user.getUid());
        return user;
    }

However, when I run the test code, I get the following error message

java.lang.NullPointerException: Cannot invoke "bytro.user.management.transformer.UserMapper.addRequestUserToEntity(bytro.user.management.model.request.AddUserRequest)" because "this.userMapper" is null

    at bytro.user.management.service.impl.UserServiceImpl.createUser(UserServiceImpl.java:64)
    at bytro.user.management.service.impl.UserServiceImpl.add(UserServiceImpl.java:53)
    at bytro.user.management.service.UserServiceTest.add(UserServiceTest.java:124)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

-when calling a method "addRequestUserToEntity" on a null object reference, "this.userMapper". The error occurred at line 64 of the "UserServiceImpl" class, which was called from line 53 of the same class. The call stack also shows that the error originated from a unit test method "add" in the "UserServiceTest" class.

The NPE usually occurs when an object reference is uninitialized or set to null, and the program attempts to access it. In this case, the "userMapper" object was not initialized or set to null in the "UserServiceImpl" class, causing the program to throw the NPE when calling the "addRequestUserToEntity" method on it.n mapper class is null


Solution

  • Your test is wrong for multiple things.

    1. Your @RunWith(SpringRunner.class) is useless and only adds to the start time of the test (or even worse you seem to be mixing JUnit4 and Junit5 in a single test class).
    2. You are overriding mocks by using annotations and manual mocking
    3. You are mocking the classes you want to inject dependencies in, you either have dependencies injected or a mock not both.
    4. Judging from the @BeforeEach you are also mixing JUnit4 and JUnit5.
    5. Your test is weird as you want to test the service but aren't testing anything as you are assuming a mock for the service. So basically you are only testing the mocking framework.

    With that change your test class.

    1. Us @ExtendWith(MockitoExtension.class) to do setup and ditch your @BeforeEach.
    2. Don't register behavior on the service as that is what you want to test.
    @ExtendWith(MockitoExtension.class)
    class UserServiceTest {
    
        @InjectMocks
        private UserServiceImpl userServiceImpl;
        
        @Mock
        private UserMapper userMapper;
    
        @Mock
        private UserRepository userRepository;
    
        @Mock
        private UserService userService;
    
        @Test
        void add() {
            AddUserRequest addUserRequest = new AddUserRequest("guest","1234567890","guest98787@guest.com","1234567890");
    
            User user = new User();
            user.setUserId(1);
            user.setUid("12345678");
            user.setName("guest");
            user.setMobileNo("1234567890");
            user.setEmail("guest98787@guest.com");
            user.setPassword("1234567890");
            WalletDetail walletDetail=new WalletDetail();
            walletDetail.setWalletId(1);
            walletDetail.setUid("12345");
            walletDetail.setUser(user);
            walletDetail.setWalletAmount(10.0);
            user.setWallet(walletDetail);
    
            when(userMapper.addRequestUserToEntity(addUserRequest)).thenReturn(user);
            when(userRepository.save(user)).thenReturn(user);
    
            User result = userServiceImpl.add(addUserRequest);
           
            assertEquals(user.getUserId(), result.getUserId());
        }
    

    NOTE: While this might fix this partially it probably will still fail. As your UserServiceImpl has more dependencies which you need to mock and inject.