Search code examples
javaspring-bootjunit

How to mock Interface in JUnit instead of its implementation class in Spring Boot


I have a problem to write JUnit Service Test shown below.

I only access UserService (Interface) and UserServiceImpl cannot be accessed from outside as it is private. This is my case.

I have a problem to call saveUser method.

Here is the structure

src
├── main
│   └── java
│       └── com
│           ├── company
│               ├── service
│                    ├── UserService.java
│                    ├── impl
│                         ├── UserServiceImpl.java (package-private)
└── test
    └── java
        └── com
            └── company
                ├── UserServiceTest.java

Here is the UserService shown below

public interface UserService {
    UserDTO saveUser(SignUpRequest signUpRequest);
}

Here is the save Method defined in UserServiceImpl

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
class UserServiceImpl implements UserService {

       private final UserRepository userRepository;

       @Transactional
       public UserDTO saveUser(SignUpRequest signUpRequest) {
       ...
       return UserDTO.builder()
                    .username(signUpRequest.getUsername())
                    .firstName(signUpRequest.getFirstName())
       }
}

Here is the UserServiceTest shown below

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

@Mock
private UserRepository userRepository;

@InjectMocks
private UserService userService;

@Test
void shouldCreateUser() {
   User user = new User();
   ...
   when(userRepository.save(any(User.class))).thenReturn(user);

   UserDTO savedUserDTO = userService.saveUser(signUpRequest);  // Here is the issue

   
}

I get this issue shown below

org.mockito.exceptions.base.MockitoException: 
Cannot instantiate @InjectMocks field named 'userService'! Cause: the type 'UserService' is an interface.
You haven't provided the instance at field declaration so I tried to construct the instance.
Examples of correct usage of @InjectMocks:
   @InjectMocks Service service = new Service();
   @InjectMocks Service service;
   //and... don't forget about some @Mocks for injection :)

When I changed userService as shown below, I get this issue.

@Mock
private UserService userService;

java.lang.NullPointerException: Cannot invoke "com.backend.user.service.dto.UserDTO.getUsername()" because "savedUserDTO" is null

How can I fix it?


Solution

  • You can't instantiate an interface in Java. While using @InjectMock you tell Mockito to instantiate your object and inject your dependency, here UserRepository.

    The first approach is to use a concrete implementation of your interface. Check this link for more details.

    @ExtendWith(MockitoExtension.class)
    class UserServiceTest {
    
        @Mock
        private UserRepository userRepository;
    
        @InjectMocks
        private UserServiceImpl userService;
    
        @Test
        void shouldCreateUser() {
       
        }
    }
    

    Also the class under test is UserService so you can't the annotation Mock on it.