模拟服务功能未按预期工作
Mocked service function not working as expected
这是我的代码。我不明白为什么它不起作用。问题在于测试中的行:
when(applicationUserRepository.findApplicationUserByUsername("testUser"))
.thenReturn(userToReturnFromRepository);
似乎没有做任何事情。函数 findApplicationUserByUsername returns 当它应该返回 userToReturnFromRepository 的可选值时是一个空的可选值。
控制器:
@RestController
@RequestMapping("api/v1/exercises")
public class ExerciseController {
@Autowired
ExerciseService exerciseService;
@GetMapping
public List<Exercise> getExercises() {
List<Exercise> exercises = exerciseService.getAllExercises();
return exercises;
}
}
服务:
@Service("exerciseService")
public class ExerciseService {
@Autowired
ExerciseRepository exerciseRepository;
@Autowired
ApplicationUserRepository applicationUserRepository;
@Transactional
public List<Exercise> getAllExercises() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
Optional<ApplicationUser> applicationUser = applicationUserRepository.findApplicationUserByUsername(principal.getName());
List<Exercise> exercises = new ArrayList<>();
if(applicationUser.isPresent()){
exercises = applicationUser.get().getExercises();
}
return exercises;
}
}
测试:
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
@Mock
ApplicationUserRepository applicationUserRepository;
@Autowired
public ExerciseControllerTest(MockMvc mockMvc,
ApplicationUserRepository applicationUserRepository, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
this.applicationUserRepository = applicationUserRepository;
this.objectMapper = objectMapper;
}
@BeforeEach
public void initMocks() {
MockitoAnnotations.openMocks(this);
}
@Test
@WithMockUser(username = "testUser")
public void testGetExercises() throws Exception {
Exercise ex = new Exercise();
ex.setData("test");
ApplicationUser user = new ApplicationUser();
Exercise[] exercises = {ex};
List<Exercise> list = new ArrayList<Exercise>(Arrays.asList(exercises));
user.setExercises(list);
Optional<ApplicationUser> userToReturnFromRepository = Optional.of(user);
when(applicationUserRepository.findApplicationUserByUsername("testUser"))
.thenReturn(userToReturnFromRepository);
mockMvc.perform(get("/api/v1/exercises")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(1)));
}
}
您的测试中发生了两件相互矛盾的事情:
- 您正在使用 Mockito 通过反射初始化模拟实现
- 您已通过构造函数将 ApplicationUserRepository Spring 注入到测试 class 中。
结果是这样的:
- spring 将 applicationUserRepository 注入构造函数参数
- applicationUserRepository 字段设置为spring构造函数中的注入版本
- Mockito 初始化一个新的 applicationUserRepository mock
- Mockito 将 applicationUserRespository 字段替换为模拟(即告别您的 MVC 设置正在使用的 spring bean 的句柄!)
我能想到的最简单的修复方法是使用@MockBean 而不是@Mock 结合构造函数注入。 @MockBean 将指示 Spring 为您创建模拟实例,使用它,并在测试中提供给您。
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
@MockBean // User MockBean instead of Mock
ApplicationUserRepository applicationUserRepository;
@Autowired
public ExerciseControllerTest(MockMvc mockMvc, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
//remove the applicationUserRepository injection
this.objectMapper = objectMapper;
}
// remove MockitoAnnotations.openMocks(this);
//...
...
}
这是我的代码。我不明白为什么它不起作用。问题在于测试中的行:
when(applicationUserRepository.findApplicationUserByUsername("testUser"))
.thenReturn(userToReturnFromRepository);
似乎没有做任何事情。函数 findApplicationUserByUsername returns 当它应该返回 userToReturnFromRepository 的可选值时是一个空的可选值。
控制器:
@RestController
@RequestMapping("api/v1/exercises")
public class ExerciseController {
@Autowired
ExerciseService exerciseService;
@GetMapping
public List<Exercise> getExercises() {
List<Exercise> exercises = exerciseService.getAllExercises();
return exercises;
}
}
服务:
@Service("exerciseService")
public class ExerciseService {
@Autowired
ExerciseRepository exerciseRepository;
@Autowired
ApplicationUserRepository applicationUserRepository;
@Transactional
public List<Exercise> getAllExercises() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
Optional<ApplicationUser> applicationUser = applicationUserRepository.findApplicationUserByUsername(principal.getName());
List<Exercise> exercises = new ArrayList<>();
if(applicationUser.isPresent()){
exercises = applicationUser.get().getExercises();
}
return exercises;
}
}
测试:
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
@Mock
ApplicationUserRepository applicationUserRepository;
@Autowired
public ExerciseControllerTest(MockMvc mockMvc,
ApplicationUserRepository applicationUserRepository, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
this.applicationUserRepository = applicationUserRepository;
this.objectMapper = objectMapper;
}
@BeforeEach
public void initMocks() {
MockitoAnnotations.openMocks(this);
}
@Test
@WithMockUser(username = "testUser")
public void testGetExercises() throws Exception {
Exercise ex = new Exercise();
ex.setData("test");
ApplicationUser user = new ApplicationUser();
Exercise[] exercises = {ex};
List<Exercise> list = new ArrayList<Exercise>(Arrays.asList(exercises));
user.setExercises(list);
Optional<ApplicationUser> userToReturnFromRepository = Optional.of(user);
when(applicationUserRepository.findApplicationUserByUsername("testUser"))
.thenReturn(userToReturnFromRepository);
mockMvc.perform(get("/api/v1/exercises")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(1)));
}
}
您的测试中发生了两件相互矛盾的事情:
- 您正在使用 Mockito 通过反射初始化模拟实现
- 您已通过构造函数将 ApplicationUserRepository Spring 注入到测试 class 中。
结果是这样的:
- spring 将 applicationUserRepository 注入构造函数参数
- applicationUserRepository 字段设置为spring构造函数中的注入版本
- Mockito 初始化一个新的 applicationUserRepository mock
- Mockito 将 applicationUserRespository 字段替换为模拟(即告别您的 MVC 设置正在使用的 spring bean 的句柄!)
我能想到的最简单的修复方法是使用@MockBean 而不是@Mock 结合构造函数注入。 @MockBean 将指示 Spring 为您创建模拟实例,使用它,并在测试中提供给您。
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
@MockBean // User MockBean instead of Mock
ApplicationUserRepository applicationUserRepository;
@Autowired
public ExerciseControllerTest(MockMvc mockMvc, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
//remove the applicationUserRepository injection
this.objectMapper = objectMapper;
}
// remove MockitoAnnotations.openMocks(this);
//...
...
}