I have a simple Spring MVC controller whose RequestMapping is a property. It's a jar that include a controller. Downstream apps will take this jar and use the common endpoint, only the precise URL can vary by app)
Everything works fine when I include my jar into another app. The app has a property or yaml file and the property is set. I've verified that the endpoint works fine.
However, being the good developer that I am, I want to make an integration test that verifies that the URL determined by the property is exposed properly. I can get an #Value in the controller injected properly, but a ${} expression in the #RequestMapping will not be substituted from a properties file. I found a couple threads (Spring Boot REST Controller Test with RequestMapping of Properties Value and #RequestMapping with placeholder not working) But either they don't apply or I tried what they said and I couldn't get it to work.
The test that hits the static (iWork) endpoint works, but the one that is pulled from the property (iDontWork) doesn't work.
(This is Spring 4.2.6)
Controller
#RestController
#RequestMapping(value = {"/${appName}", "/iWork"})
public class Controller {
#Value("${appName}")
private String appName;
#RequestMapping(method= RequestMethod.GET)
public String handlerMethod (HttpServletRequest request) throws Exception {
// Proves the placeholder is injected in the class, but
// Not in the RequestMapping
assert appName != null;
assert !appName.equals("${appName}");
return "";
}
}
ControllerTest
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigWebContextLoader.class,
classes = { ControllerTest.Config.class })
public class ControllerTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
#Configuration
#ComponentScan(basePackages = {"test"})
static class Config {
// because #PropertySource doesnt work in annotation only land
#Bean
PropertyPlaceholderConfigurer propConfig() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocation(new ClassPathResource("test.properties"));
return ppc;
}
}
#Test
public void testStaticEndpoint() throws Exception {
mvc.perform(get("/iWork")).andExpect(status().isOk());
}
#Test
public void testDynamicEndpoint() throws Exception {
mvc.perform(get("/iDontWork")).andExpect(status().isOk());
}
}
test.properties
appName = iDontWork
You're "simply" missing
#EnableWebMvc
on your #Configuration class. Without it, Spring's Mock MVC stack will register your controller handler methods with DefaultAnnotationHandlerMapping which isn't smart enough to resolve the placeholders in #RequestMapping.
If you do provide it, Mock MVC will instead use RequestMappingHandlerMapping, which is.
You need to add placeholder value while creating mockMvc as shown below.
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(accountController)
.addPlaceholderValue("propertyName", "propertyValue")
.build();
}
Related
Using SpringBoot, I have an ErrorController that is supposed to redirect all invalid requests to a certain page (the swagger documentation in this case). In practice this works:
#Controller
public class MyErrorController implements ErrorController {
#RequestMapping("/error")
public RedirectView redirectErrorToSwaggerPage() {
return new RedirectView("/swagger-ui/index.html");;
}
}
I would like to write a test for this behavior, so I wrote this:
#AutoConfigureTestEntityManager
#SpringBootTest
#ContextConfiguration(classes = { MyTestContext.class })
#TestPropertySource(properties = { "spring.main.allow-bean-definition-overriding=true" })
#AutoConfigureMockMvc
class MyErrorControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
void getWithWrongPathtShouldRedirectToSwaggerPage() throws Exception {
mockMvc.perform(get("/pathThatDoesNotExist"))
.andExpect(redirectedUrl("/swagger-ui/index.html"));
}
}
However, this test fails with the following error:
java.lang.AssertionError: Redirected URL expected:</swagger-ui/index.html> but was:<null>
I also tried some of the suggestions on this related page, but was not able to get the test to pass with any of those either: https://github.com/spring-projects/spring-boot/issues/5574
Is this simply not possible? Or how can I do this?
Spring Boot here. I currently have the following REST controller:
#RestController
public class FizzbuzzController {
private final FizzbuzzService FizzbuzzService;
public FizzbuzzController(FizzbuzzService FizzbuzzService) {
this.FizzbuzzService = FizzbuzzService;
}
#PostMapping("/Fizzbuzzs/{fizzbuzzId}")
public ResponseEntity<FizzbuzzDTO> addFizzbuzz(#RequestParam("files") List<MultipartFile> files,
#PathVariable String fizzbuzzId) throws IOException {
FizzbuzzDTO fizzbuzzDTO = fizzbuzzService.store(files, fizzbuzzId);
return ResponseEntity.status(HttpStatus.OK).body(fizzbuzzDTO);
}
}
I would like to write an integration test for it that:
Mocks or stubs an HTTP request to the URL; and
Allows me to inject the FizzbuzzController (under test) with a mocked FizzbuzzService or the real thing; and
Allows me to inspect the HTTP response coming back from the method (check status code, check response entity, etc.)
My best attempt thus far:
#WebMvcTest(FizzbuzzController.class)
#EnableConfigurationProperties
#AutoConfigureMockMvc
public class FizzbuzzControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FizzbuzzService FizzbuzzService;
#Test
public void should_store_fizzbuzz_files() throws Exception {
// I can't even get the test to run
assertTrue(1 == 1);
}
}
When I run this, the test fails to run and it is clear (looking at the logs) that Spring is loading the entire application context of my app, whereas I just want it to isolate the context to this test class, the main FizzbuzzController class, and anything in the dependency tree underneath it.
Can anyone spot where I'm going awry?
You need another context for testing. I'm suggesting you to have a separate Test config:
#TestConfiguration
#Slf4j
#EnableJpaRepositories("tth.patientportal.repository")
public class TestConfig { // bean configs goes here for testing if you need to change
// context}
and in a controller test build the context like below:
#RunWith(SpringRunner.class)
#AutoConfigureTestEntityManager
#SpringBootTest
#TestPropertySource("classpath:application-unittest.properties")
#ContextConfiguration(classes = {TestConfig.class})
public class RestControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup()
{
mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void shouldReturnRegisteredUser() throws Exception {
this.mockMvc.
perform(MockMvcRequestBuilders
.post("url")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").exists());
}
}
I am using spring-boot-1.5.10 along with spring-security-test & spring-boot-hateoas and JUnit-5. I have some custom Jackson configuration in my application.MockMvc in unit test not picking those custom Jackson configuration. I would like to know how to inject those configurations in MockMvc along with that I would like to know how to inject custom Jackson module in MockMvc. Please find the below code for reference,
#RunWith(SpringRunner.class)
#WebMvcTest(BookController.class)
public class BookControllerTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#MockBean
private BookService bookService;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity()) //Here i would also like to configure Jackson
.build();
}
#Test
#WithMockApiUser(roles = {"API_ADMIN"})
public void shouldGetBook() throws Exception {
BookResponse bookResponse = BookResponse.builder()
.basicInfo(getBasicInfo())
.extendedInfo(getExtendedInfo())
.build();
given(bookService.getBook(apiAuthentication.getApiContext(),"bookUid")).willReturn(bookResponse);
System.out.println("Input ::"+objectMapper.writeValueAsString(bookResponse)); //Here the json string response looks fine.
MvcResult result = mockMvc.perform(get("/v1/books/bookUid"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/hal+json"))
.andExpect(jsonPath("bookUid").value("bookUid")).andReturn();
System.out.println("response::"+objectMapper.writeValueAsString(result.getResponse().getContentAsString())); //Here the content string is not properly converted by Jackson
verifyNoMoreInteractions(bookService);
}
}
JACKSON CONFIGURATION CLASS
#Configuration
public class ObjectMapperConfiguration {
#Autowired
private ObjectMapper objectMapper;
#PostConstruct
public void init() {
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
}
I have googled about this issue but all the links use StandaloneSetUp but in my application we are using spring-security so I would like to go with the web application context.
Any help or hint would be appreciable.
I will post below what has been worked for me so far.
Test classes are annotated with:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#TestPropertySource(properties = {
"app.config1.enable=false"
, "app.xonfig2.enable=false"
})
This is helpful when I want to enable or disable a configuration during this test suite. In order for this to work, the Configuration classes must contain:
#Configuration
#ConditionalOnProperty(
value = "app.config1.enable", havingValue = "true", matchIfMissing = true
)
public class MyConfig1 {
.
.
.
}
Applying this in your case, just enable the Jackson configuration and it should work. If you don't want to enable configurations individually, skip this part and add only the #SpringBootTest(classes = {MyApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) line.
I hope it helps.
EDIT: This question is specifically pertaining to the #RestClientTest annotation introduced in spring-boot 1.4.0 which is intended to replace the factory method.
Problem:
According to the documentation the #RestClientTest should correctly configure a MockRestServiceServer to use when testing a REST client. However when running a test I am getting an IllegalStateException saying the MockServerRestTemplateCustomizer has not been bound to a RestTemplate.
Its worth noting that I'm using Gson for deserialization and not Jackson, hence the exclude.
Does anyone know how to correctly use this new annotation? I haven't found any examples that require more configuration then when I have already.
Configuration:
#SpringBootConfiguration
#ComponentScan
#EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class})
public class ClientConfiguration {
...
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
}
Client:
#Service
public class ComponentsClientImpl implements ComponentsClient {
private RestTemplate restTemplate;
#Autowired
public ComponentsClientImpl(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public ResponseDTO getComponentDetails(RequestDTO requestDTO) {
HttpEntity<RequestDTO> entity = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO> response =
restTemplate.postForEntity("/api", entity, ResponseDTO.class);
return response.getBody();
}
}
Test
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private MockRestServiceServer server;
#Test
public void getComponentDetailsWhenResultIsSuccessShouldReturnComponentDetails() throws Exception {
server.expect(requestTo("/api"))
.andRespond(withSuccess(getResponseJson(), APPLICATION_JSON));
ResponseDTO response = client.getComponentDetails(requestDto);
ResponseDTO expected = responseFromJson(getResponseJson());
assertThat(response, is(expectedResponse));
}
}
And the Exception:
java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate
Answer:
As per the answer below there is no need to declare a RestTemplateBuilder bean into the context as it is already provided by the spring-boot auto-configuration.
If the project is a spring-boot application (it has #SpringBootApplication annotation) this will work as intended. In the above case however the project was a client-library and thus had no main application.
In order to ensure the RestTemplateBuilder was injected correctly in the main application context (the bean having been removed) the component scan needs a CUSTOM filter (the one used by #SpringBootApplication)
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
The MockRestServiceServer instance should be constructed from the static factory, using a RestTemplate. See this article for a detailed description of the testing process.
In your example, you can do:
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private RestTemplate template;
private MockRestServiceServer server;
#Before
public void setUp() {
server= MockRestServiceServer.createServer(restTemplate);
}
/*Do your test*/
}
You have RestTemplateBuilder at two places. At ClientConfiguration class and at ComponentsClientImpl class. Spring boot 1.4.0 auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. Remove below code from ClientConfiguration class and run your test.
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I have created a test case using Mockito.I want to test a controller.When the controller is called i want to send back a responce object and dont want the code inside controller to execute.But even though i have used when(functionName).thenReturn(), its getting into the controllers code.What am i doing wrong here?
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class MockitoController {
#Autowired
private WebApplicationContext webApplicationContext;
protected MockMvc mockMvc;
#SuppressWarnings("unchecked")
#Before
public void setup() {
MyController myController = Mockito.mock(myController.class);
ResponseView jsonResponse = new ResponseView();
jsonResponse.setStatus(1);
jsonResponse.setMessage("true");
Mockito.when((myController.deleteMedia(Mockito.anyInt()))).thenReturn(jsonResponse);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
}
#Test
public void deleteMediaMockito() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.delete("/library/99")
.accept(MediaType.APPLICATION_JSON);
MvcResult result = this.mockMvc.perform(requestBuilder).andReturn();
JSONObject jsonObject = new JSONObject(result.getResponse().getContentAsString());
assertEquals(1, jsonObject.get("status"));
}
}
I would try a local class containing your mock controller. Something like this inside your test class. This will register your mock and overwrite the real MyController inside the application context.
#Configuration
public static class MyMockConfig {
#Bean
#Primary
MyController myController() {
Mockito.mock(MyController.class);
}
}
But I am not convinced that the thing you are testing makes sense. MockMvc is used to test your controller as a whole including the requestMappings etc.
So why would you mock parts of it. The controller is your unit under test. Try to mock away its dependencies.
But still - the code I posted can help to inject mocks into spring beans.
Try changing the Mockito.anyInt() method for a constant here:
Mockito.when((myController.deleteMedia(Mockito.anyInt()))).thenReturn(jsonResponse);
This method should be used for matching purposes, not to provide values. I had a situation similar to yours, the tests/whens were behaving really strange, and began doing what I was expecting to only after I replace them for constants.
I hope it helps!