I am trying to test a Spring Boot Controller, the controller has an UserService
dependency and the UserService
has an UserMapper
dependency, the code compiles and run flawless, but when trying to run the test below i get an error:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userControllerImpl':
Unsatisfied dependency expressed through field 'userMapper': Error creating bean with name 'com.acneUserMapper':
Failed to instantiate [com.acne.user.UserMapper]: Specified class is an interface
test:
@WebMvcTest(UserController.class)
@Import(UserMapper.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@AutoConfigureWebMvc
public class UserControllerTests {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldGetAuthenticatedUser() throws Exception {
this.mockMvc.perform(get("/api/users/me").with(oidcLogin())).andExpect(status().isOk());
}
}
UserMapper:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserMapper {
public UserResponseDTO toUserResponseDTO(UserEntity userEntity);
public UserDTO toDto(UserEntity entity);
public UserEntity toEntity(UserDTO dto);
}
UserController & Impl:
public interface UserController {
@GetMapping("/me")
public ResponseEntity<UserResponseDTO> getCurrentUser(Authentication authentication);
}
@RestController
public class UserControllerImpl implements UserController {
@Autowired
UserMapper userMapper;
@Autowired
UserService userService;
@Override
public ResponseEntity<UserResponseDTO> getCurrentUser(Authentication currentUser) {
UserEntity loggedUser = userService.findByLoginOrError(currentUser.getName());
return ResponseEntity.ok(userMapper.toUserResponseDTO(loggedUser));
}
}
If i change the test import to @Import(UserMapperImpl.class)
the error changes to:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.acne.user.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency a
nnotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
My question is: What can I do to test the controller and spring correctly inject the mapper?
Using @WebMvcTest
and @MockitoBean
for UserService
is usually the preferred alternative.
@WebMvcTest(UserController.class)
@Import(UserMapperImpl.class)
class UserControllerTest {
@MockitoBean
UserService userService;
@Autowired
MockMvc mockMvc;
You will have to implement required methods on the mock like this
when(userService.findByLoginOrError("user1")).thenReturn(<SOME VALUE HERE>);
If you want to mock UserMapper
as well, remove @Import(UserMapperImpl.class)
and add
@MockitoBean
UserMapper userMapper;
Alternative Option
@SpringBootTest
and @AutoConfigureMockMvc
as documented here.
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTests {
@Autowired
MockMvc mockMvc;
// remaining code left out
Both @MockitoBean
and @MockitoSpyBean
can also be used with @SpringBootTest
.
Tested on my computer with this in build.gradle.kts:
compileOnly("org.mapstruct:mapstruct:1.6.3")
annotationProcessor("org.mapstruct:mapstruct-processor:1.6.3")