将模拟的 Spring 引导控制器替换为实际控制器



我是 Spring Boot and Testing的新手。

tl;dr 如何在 Spring 启动应用程序中将@MockBean控制器替换为实际控制器,以便我可以测试控制器是否正常工作,而不仅仅是测试我的对象是否正确输出?

我正在编写一个带有依赖项的 gradle 托管 API(来自 build.gradle(:

// Spring Boot (2.0.5 Release)
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-hateoas')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('org.springframework.boot:spring-boot-devtools')
// Testing
testImplementation('org.junit.jupiter:junit-jupiter-api:5.3.1')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.3.1')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile("org.assertj:assertj-core:3.11.1")
testCompile 'org.mockito:mockito-core:2.+'

我有一个带有以下相关代码的 API 控制器类:

@Controller
public class ObjectivesApiController extends AbstractRestHelperFunctionality implements ObjectivesApi {
protected ObjectivesApiController(
UserRepository userRepository,
CompaniesRepository companiesRepository,
TeamsRepository teamsRepository,
ProjectsRepository projectsRepository,
OdiAssessmentRepository odiAssessmentRepository,
OdiCustomerRatingRepository odiCustomerRatingRepository,
OdiTechRatingRepository odiTechRatingRepository,
OdiValueRatingRepository odiValueRatingRepository,
ObjectivesRepository objectivesRepository,
KeyResultRepository keyResultRepository) {
super(
userRepository,
companiesRepository,
teamsRepository,
projectsRepository,
odiAssessmentRepository,
odiCustomerRatingRepository,
odiTechRatingRepository,
odiValueRatingRepository,
objectivesRepository,
keyResultRepository);
}
public ResponseEntity<KeyResult> createKeyResult(@ApiParam(value = "id", required = true) @PathVariable("id") Long id, @ApiParam(value = "keyResult", required = true) @Valid @RequestBody KeyResult keyResultDTO) {
KeyResult keyResult = KeyResultBuilder
.aKeyResult()
.withDescription(keyResultDTO.getDescription())
.withCompleted(keyResultDTO.getCompleted())
.build();
Objective parentObjective = objectivesRepository.findByObjectiveId(id);
parentObjective.addKeyResult(keyResult);
keyResultRepository.save(keyResult);
objectivesRepository.save(parentObjective);
return new ResponseEntity<KeyResult>(HttpStatus.CREATED);
}
public ResponseEntity<Objective> createObjective(@ApiParam(value = "objective", required = true) @Valid @RequestBody Objective objectiveDTO) {
Objective objective = ObjectiveBuilder
.anObjective()
.withDescription(objectiveDTO.getDescription())
.withCompleted(objectiveDTO.getCompleted())
.withKeyResults(objectiveDTO.getKeyResults())
.build();
objective.getKeyResults().forEach(keyResultRepository::save);
objectivesRepository.save(objective);
return new ResponseEntity<Objective>(HttpStatus.CREATED);
}
public ResponseEntity<Void> deleteAllLinkedKeyResults(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
Objective subjectObjective = objectivesRepository.findByObjectiveId(id);
subjectObjective.getKeyResults().clear();
objectivesRepository.save(subjectObjective);
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
public ResponseEntity<Void> deleteObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
objectivesRepository.delete(objectivesRepository.findByObjectiveId(id));
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
public ResponseEntity<Void> deleteOneKeyResult(@ApiParam(value = "the id of the objective you want key results for", required = true) @PathVariable("objectiveId") Long objectiveId, @ApiParam(value = "the id of the key result", required = true) @PathVariable("keyResultId") Long keyResultId) {
Objective subjectObjective = objectivesRepository.findByObjectiveId(objectiveId);
KeyResult keyResult = keyResultRepository.findByKeyResultId(keyResultId);
subjectObjective.removeKeyResult(keyResult);
objectivesRepository.save(subjectObjective);
keyResultRepository.delete(keyResult);
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
public ResponseEntity<List<Objective>> getAllObjectives() {
List<Objective> allObjectives = objectivesRepository.findAll();
return new ResponseEntity<List<Objective>>(allObjectives, HttpStatus.OK);
}
public ResponseEntity<List<KeyResult>> getKeyResultsForObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
Objective subjectObjective = objectivesRepository.findByObjectiveId(id);
List<KeyResult> allKeyResults = subjectObjective.getKeyResults();
return new ResponseEntity<List<KeyResult>>(allKeyResults, HttpStatus.OK);
}
public ResponseEntity<Objective> getObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
Objective subjectObjective = objectivesRepository.findByObjectiveId(id);
return new ResponseEntity<Objective>(subjectObjective, HttpStatus.OK);
}
public ResponseEntity<KeyResult> getKeyResultForObjective(@ApiParam(value = "the id of the objective you want key results for", required = true) @PathVariable("objectiveId") Long objectiveId, @ApiParam(value = "the id of the key result", required = true) @PathVariable("keyResultId") Long keyResultId) {
Objective subjectObjective = objectivesRepository.findByObjectiveId(objectiveId);
KeyResult subjecKeyResult = subjectObjective.getKeyResults().stream()
.filter(KeyResult -> keyResultId.equals(KeyResult.getKeyResultId()))
.findFirst()
.orElse(null);
return new ResponseEntity<KeyResult>(subjecKeyResult, HttpStatus.OK);
}
public ResponseEntity<Objective> updateObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id, @ApiParam(value = "objective", required = true) @Valid @RequestBody Objective objectiveDTO) {
Objective existingObjective = objectivesRepository.findByObjectiveId(id);
Objective objective = ObjectiveBuilder
.anObjective()
.withObjectiveId(existingObjective.getObjectiveId())
.withDescription(objectiveDTO.getDescription())
.withCompleted(objectiveDTO.getCompleted())
.withKeyResults(objectiveDTO.getKeyResults())
.build();
objective.getKeyResults().forEach(keyResultRepository::save);
objectivesRepository.save(objective);
return new ResponseEntity<Objective>(HttpStatus.NO_CONTENT);
}
public ResponseEntity<KeyResult> updateKeyResult(@ApiParam(value = "the id of the objective you want key results for", required = true) @PathVariable("objectiveId") Long objectiveId, @ApiParam(value = "the id of the key result", required = true) @PathVariable("keyResultId") Long keyResultId, @ApiParam(value = "keyResult", required = true) @Valid @RequestBody KeyResult keyResultDTO) {
if (objectivesRepository.existsById(objectiveId) && keyResultRepository.existsById(keyResultId)) {
Objective subjectObjective = objectivesRepository.findByObjectiveId(objectiveId);
KeyResult subjecKeyResult = subjectObjective.getKeyResults().stream()
.filter(KeyResult -> keyResultId.equals(KeyResult.getKeyResultId()))
.findFirst()
.orElse(null);
KeyResult updatedKeyResult = KeyResultBuilder
.aKeyResult()
.withKeyResultId(subjecKeyResult.getKeyResultId())
.withDescription(keyResultDTO.getDescription())
.withCompleted(keyResultDTO.getCompleted())
.build();
keyResultRepository.save(updatedKeyResult);
Collections.replaceAll(subjectObjective.getKeyResults(), subjecKeyResult, updatedKeyResult);
objectivesRepository.save(subjectObjective);
}
return new ResponseEntity<KeyResult>(HttpStatus.NO_CONTENT);
}
}

对于此类的上下文,AbstractRestHelper 超类所做的所有工作都是创建我的存储库的单例,然后将其...字段注入(不确定这是否是正确的术语(到控制器中。此模式在所有控制器中重复,因此混乱。

正在实现的 API 是一个 Swagger 2 API 接口,该接口尽可能使该控制器没有注释。

最后一块是测试类。这是我问题的核心。

@ExtendWith(SpringExtension.class)
@WebMvcTest(ObjectivesApiController.class)
class ObjectivesApiControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ObjectivesApiController objectivesApiControllerMock;
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void getAllObjectives() throws Exception {
// Create two objects to test with:
Objective testObjective1 = ObjectiveBuilder
.anObjective()
.withObjectiveId(1L)
.withDescription("Test Objective")
.withCompleted(false)
.build();
Objective testObjective2 = ObjectiveBuilder
.anObjective()
.withObjectiveId(2L)
.withDescription("Test Objective")
.withCompleted(true)
.build();
List<Objective> testList = new ArrayList<Objective>();
testList.add(testObjective1);
testList.add(testObjective2);
// Set expectations on what should be found:
when(objectivesApiControllerMock.getAllObjectives()).thenReturn(new ResponseEntity<List<Objective>>(testList, HttpStatus.OK));
// Carry out the mocked API call:
mockMvc.perform(get("/objectives"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].objectiveId", is(1)))
.andExpect(jsonPath("$[0].description", is("Test Objective")))
.andExpect(jsonPath("$[0].completed", is(false)))
.andExpect(jsonPath("$[1].objectiveId", is(2)))
.andExpect(jsonPath("$[1].description", is("Test Objective")))
.andExpect(jsonPath("$[1].completed", is(true)));

// Validate the response is what we expect:
verify(objectivesApiControllerMock, times(1)).getAllObjectives();
verifyNoMoreInteractions(objectivesApiControllerMock);
}
@Test
void getKeyResultsForObjective() throws Exception {
KeyResult testKeyResultWithParentObjective1 = KeyResultBuilder
.aKeyResult()
.withKeyResultId(1L)
.withCompleted(false)
.withDescription("My parent Key Result is 1")
.build();
KeyResult testKeyResultWithParentObjective2 = KeyResultBuilder
.aKeyResult()
.withKeyResultId(2L)
.withCompleted(true)
.withDescription("My parent Key Result is 1")
.build();
Objective testObjectiveWithKeyResults = ObjectiveBuilder
.anObjective()
.withObjectiveId(1L)
.withDescription("Test Objective")
.withKeyResults(new ArrayList<KeyResult>())
.withCompleted(false)
.build();
testObjectiveWithKeyResults.addKeyResult(testKeyResultWithParentObjective1);
testObjectiveWithKeyResults.addKeyResult(testKeyResultWithParentObjective2);
when(objectivesApiControllerMock.getKeyResultsForObjective(1L)).thenReturn(new ResponseEntity<List<KeyResult>>(testObjectiveWithKeyResults.getKeyResults(), HttpStatus.OK));
mockMvc.perform(get("/objectives/1/keyresult"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].keyResultId", is(1)))
.andExpect(jsonPath("$[0].description", is("My parent Key Result is 1")))
.andExpect(jsonPath("$[0].completed", is(false)))
.andExpect(jsonPath("$[1].keyResultId", is(2)))
.andExpect(jsonPath("$[1].description", is("My parent Key Result is 1")))
.andExpect(jsonPath("$[1].completed", is(true)));
}
}

我的问题是这样的: 在使用 Mockito 模拟目标控制器以验证我的对象是否正确形成之后,我现在想做同样的事情,但我想实际测试控制器,而不是模拟。

您认为让它工作的最天真的方法是什么(我可以稍后重构(。我搜索的资源要么使用不同版本的Junit,要么依赖于mockito而不是实际的控制器。

没有什么很合适 - 由于控制器被模拟,我实际上并没有涵盖任何代码,所以测试毫无价值,对吧?我唯一要看的是对象是否正确形成,我现在需要检查控制器是否正常运行,并且正在返回格式正确的对象。

有没有人做过任何类似的事情?您使用哪些方法来管理现场注入控制器的测试?

对此的任何建议将不胜感激。我很想了解从事生产级应用程序工作的人如何处理带有控制器、存储库等的 Spring 启动应用程序的测试。

非常感谢!

你可以使用@SpyBean。这样,您既可以按原样使用它,也可以模拟某些调用。https://www.baeldung.com/mockito-spy

最新更新