Search code examples
javaspring-bootunit-testingjunitmockito

Mockito mock returning Null value from repository


I'm trying to test my service implementation with JUNIT5. I don't quite understand whats going wrong but my UserRepository doesn't seem to be returning a value.

I have tested to see if the UserRepository has been used with: verify(repository, times(1)); And the response was "Wanted but not invoked...Actually, there were zero interactions with this mock"

Here is the Test Class:

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @Mock
    UserRepository repository;
    @InjectMocks
    UserServiceImpl userServiceImpl;

    UserModel inputUserModel;

    @BeforeEach
    public void setUp() throws Exception {
        inputUserModel = new UserModel();
        inputUserModel.setEmail("[email protected]");
        inputUserModel.setFirstName("john");
        inputUserModel.setLastName("doe");
        inputUserModel.setPassword("test");
        inputUserModel.setMatchPassword("test");

        User inputUser = User.builder()
                .email(inputUserModel.getEmail())
                .firstName(inputUserModel.getFirstName())
                .lastName(inputUserModel.getLastName())
                .password(inputUserModel.getPassword())
                .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))).userID(1)
                .build();

        User outputUser = User.builder().
                email(inputUserModel.getFirstName()).
                password(inputUserModel.getLastName()).
                lastName(inputUserModel.getFirstName())
                .firstName(inputUserModel.getFirstName())

                .timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))).userID(1).build();

        Mockito.when(repository.save(inputUser)).thenReturn(outputUser);

    }

    @Test
    public void whenSaveUser_ThenUserHasID(){

        Assertions.assertEquals(1, userServiceImpl.saveUser(inputUserModel).getUserID());
    }
}

The error that I am getting:

org.mockito.exceptions.misusing.PotentialStubbingProblem: 
Strict stubbing argument mismatch. Please check:
 - this invocation of 'save' method:
    repository.save(
    User(userID=0, [email protected], timeCreated=2022-12-14 03:18:24.0435578, password=test, firstName=john, lastName=doe)
);
    -> at com.jschwery.securitydemo.service.Implementations.UserServiceImpl.saveUser(UserServiceImpl.java:37)
 - has following stubbing(s) with different arguments:
    1. repository.save(
    User(userID=1, [email protected], timeCreated=2022-12-14 03:18:24.0235563, password=test, firstName=john, lastName=doe)
);

My Service Class that I'm creating the test for:

public class UserServiceImpl implements UserService {

    UserRepository  userRepository;

    @Autowired
    public UserServiceImpl(UserRepository repository){
        this.userRepository = repository;
    }

    @Override
    public User saveUser(UserModel userModel) {
        if(!Objects.equals(userModel.getPassword(), userModel.getMatchPassword())){
            throw new UserException("Passwords do not match");
        }
        User user = User.builder().
                email(userModel.getEmail()).
                firstName(userModel.getFirstName()).
                lastName(userModel.getLastName()).
                password(userModel.getPassword()).
                timeCreated(Timestamp.valueOf(LocalDateTime.now(ZoneId.systemDefault()))).build();
        User returnedUser = userRepository.save(user);
        System.out.println(returnedUser.getEmail());
        System.out.println("userID" + returnedUser.getUserID());
        return returnedUser;
    }
}

Thanks for reading! :)


Solution

  • I guess that your User class has an equals/hashCode defined either on the userID field or on all fields. When you mock your repository in your setUp method, you define what the method should do when it is called with the exact object that you specified, comparing the given object to the object specified in the mock by calling the equals/hashCode method.

    You define the User object in your setUp method to have the userID 1 but in your UserServiceImpl the userID is not ever set (as it is generated by the persistence layer). Therefore the equals/hashCode check to determine if Mockito should execute the stub logic will never be called because the object passed by the UserServiceImpl will never be equals to the one that you defined in your mock.

    There are several ways how you can solve this.

    1) Integration test

    My advice would be to convert your Unit-Test to a @SpringBootTest. There is no value in testing mocked behaviour. If this test was an integration test, you would test that the repository inserts the User into the database and generates an ID with your assertion.

    2) Unit test

    If you want to mock the repository, you should use an any() matcher for the input argument and then do what the repository would do, namely set the id:

      when(repository.save(any(User.class))).thenAnswer(invocation -> {
                final User entity = invocation.getArgument(0);
                ReflectionTestUtils.setField(entity, "userID", RandomUtils.nextLong());
                return entity;
            });
    

    I would then assert, that the userID is not null, as the userID will be randomly generated as it would in production:

    @ExtendWith(MockitoExtension.class)
    public class UserServiceTest {
    
        @Mock
        private UserRepository repository;
    
        @InjectMocks
        private UserServiceImpl userServiceImpl;
    
        @Test
        void saveUser_userHasID(){
            // Arrange
            final UserModel inputUserModel = new UserModel();
            inputUserModel.setEmail("[email protected]");
            inputUserModel.setFirstName("john");
            inputUserModel.setLastName("doe");
            inputUserModel.setPassword("test");
            inputUserModel.setMatchPassword("test");
    
            when(repository.save(any(User.class))).thenAnswer(invocation -> {
                final User entity = invocation.getArgument(0);
                ReflectionTestUtils.setField(entity, "userID", RandomUtils.nextLong());
                return entity;
            });
    
            // Act
            final User user = userServiceImpl.saveUser(inputUserModel);
    
            // Assert
            assertThat(user.getUserID()).isNotNull();
        }
    }
    
    

    I highly recommend to use AssertJ for your assertions as it offers fluent asssertions and way more assertion methods than JUnit.