When generating integration tests for Spring controllers, we don't call method directly, but rather access it via mockMvc.perform(requestBuilder) where via requestBuilder we configure request method, path and other parameters. We do that, because mockMvc closer resembles production environment compared to a direct controller method call, since mockMvc also tests controller method resolving and validates input data the same way it's done in production environment.
However, spring-web is an extremely large module and a huge part of it isn't yet supported in UtBot, which limits our ability to convert direct method call to indirect call via mockMvc.perform(requestBuilder), so if we are unable to do that conversion it's desired to fallback to a regular flow with direct calls.
This PR implements such fallback. Now whenever we generate integration tests for controller method (i.e. when we get non-null result from SpringModelUtils.createRequestBuilderModelOrNull) we:
spend first half of the fuzzing time trying to use mockMvc
spend the second half of the fuzzing time using mockMvciff we do support all parameters of method under test and during the first half of the fuzzing time we were able to get an execution that didn't throw an exception and had an HTTP status code smaller than 400
otherwise spend the second half of the fuzzing time using direct calls
prior to minimization remove UtExecutions that use direct call and have the same coverage of method under test and same successfulness as some other UtExecution that uses mockMvc (only coverage inside method under test is considered because when mockMvc is used Spring may execute additional code before/after method under test which will cause coverage differ from the one obtained from direct call)
We fallback to direct calls when we only get failing executions and successful executions with HTTP status code greater or equal to 400, because sometimes even though we technically support all the parameters, we may still be unable to trigger method under test via mockMvc because we are unable to pass some validation (i.e. because [user narrowed the primary mapping with not yet supported headers = ... in their @RequestMapping annotation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#headers())), see first example in How to test.
We still spend half of the time fuzzing with mockMvc even when we know that we don't support some of the method under test parameters, because oftentimes Spring is able to provide reasonable defaults enabling us to still get some coverage with mockMvc (we prefer executions obtained from mockMvc because they can actually be reproduced in production), see second example in How to test.
How to test
Manual tests
Place the following method in the OwnerController class from spring-petclinic project
Description
Fixes #2557
When generating integration tests for Spring controllers, we don't call method directly, but rather access it via
mockMvc.perform(requestBuilder)
where viarequestBuilder
we configure request method, path and other parameters. We do that, becausemockMvc
closer resembles production environment compared to a direct controller method call, sincemockMvc
also tests controller method resolving and validates input data the same way it's done in production environment.However,
spring-web
is an extremely large module and a huge part of it isn't yet supported in UtBot, which limits our ability to convert direct method call to indirect call viamockMvc.perform(requestBuilder)
, so if we are unable to do that conversion it's desired to fallback to a regular flow with direct calls.This PR implements such fallback. Now whenever we generate integration tests for controller method (i.e. when we get non-
null
result fromSpringModelUtils.createRequestBuilderModelOrNull
) we:mockMvc
mockMvc
iff we do support all parameters of method under test and during the first half of the fuzzing time we were able to get an execution that didn't throw an exception and had an HTTP status code smaller than400
UtExecution
s that use direct call and have the same coverage of method under test and same successfulness as some otherUtExecution
that usesmockMvc
(only coverage inside method under test is considered because whenmockMvc
is used Spring may execute additional code before/after method under test which will cause coverage differ from the one obtained from direct call)We fallback to direct calls when we only get failing executions and successful executions with HTTP status code greater or equal to
400
, because sometimes even though we technically support all the parameters, we may still be unable to trigger method under test viamockMvc
because we are unable to pass some validation (i.e. because [user narrowed the primary mapping with not yet supportedheaders = ...
in their@RequestMapping
annotation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#headers())), see first example in How to test.We still spend half of the time fuzzing with
mockMvc
even when we know that we don't support some of the method under test parameters, because oftentimes Spring is able to provide reasonable defaults enabling us to still get some coverage withmockMvc
(we prefer executions obtained frommockMvc
because they can actually be reproduced in production), see second example in How to test.How to test
Manual tests
Place the following method in the
OwnerController
class from spring-petclinic projectGenerate integration tests with
PetClinicApplication
config, there should be tests like this:@Test @DisplayName("initFindForm: ") public void testInitFindForm1() throws Exception { Object[] uriVariables = {}; MockHttpServletRequestBuilder mockHttpServletRequestBuilder = get("/owners/find", uriVariables);
}
Self-check list