I am in the middle of converting my code from using Spring field injection to using constructor injection, mainly for testing and mocking purposes. I am having issues with my mappers though, since MapStruct only seems to use Spring field injection.
Summary
- Is there a way to define a parameterized constructor for a Mapper
- Is there a way to use any other Spring injection other than field when using other mappers
Details
This is what I basically have now (minus irrelevant and implicit code)
public class User {
private Set<Role> roles;
}
public class Role {
private String name;
}
@Service
public class RoleService {
public Role getRoleByName(String roleName) { // impl }
}
public class UserViewModel {
private Set<String> roles;
}
@Mapper(uses = "RoleMapper.class")
public interface UserMapper {
UserViewModel userToUserViewModel(User user);
User userViewModelToUser(UserViewModel userViewModel);
}
@Mapper
public abstract class RoleMapper {
protected RoleService roleService;
public Role roleNameToRole(String roleName) {
Role role = roleService.getRoleByName(roleName);
if (role == null) {
throw new RuntimeException(MessageFormat.format("Unknown role {0}", roleName));
}
return role;
}
public String roleToRoleName(Role role) {
return role == null ? null : role.getName();
}
@Autowired
public void setRoleService(RoleService roleService) {
this.roleService = roleService;
}
}
As you can see I am using setter injection for the RoleMapper instead of constructor injection since compilation fails if I try and define a parameterized constructor. So my first question is "Is there a way to define a parameterized constructor for a Mapper?" (my guess is no since even if there were Mappers.getMapper only has a class as a parameter, with no way to send parameters)
The main issue I'm having is that the uses = "RoleMapper.class" (using Spring) generates an @Autowired private RoleMapper roleMapper; in the UserMapperImpl which works fine except that I can't mock that roleService properly. If I do a UserMapper userMapper = new UserMapperImpl() in my tests I can't inject a RoleMapper and if I use @RunWith(SpringJUnit4ClassRunner.class) to autowire it and inject mocks then, being that it has a singleton scope, it affects other tests that need it not to be mocked. Ideally what I would want is constructor (or even setter) injection so that I can construct exactly what I need for some tests while leaving the Spring bean alone.