Search code examples
javaspringspring-bootspring-mvcmapstruct

How to inject a Mapper from MapStruct into a @WebMockMVC Integration Test


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?


Solution

  • 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")