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.
Related
I have tried with #Autowired on the objectMapper, also tried to Mock it but no success, I just want to use the writeValueAsStringMethod so I do not have to pass a long json string to the content method below.
If I mark my class with #SpringBootTest and also #AutoconfigureMockMvc it works (the objectmapper is not null) but I believe that there must be another way so that it does not become mandatory to use this annotations.
TestClass:
#ExtendWith(MockitoExtension.class)
public class CarControllerTest {
private MockMvc mockMvc;
#InjectMocks
private CarController carController;
#Mock
private ObjectMapper objectMapper;
#MockBean
private CarParts carParts;
#BeforeEach
public void before() {
mockMvc = MockMvcBuilders.standaloneSetup(carController).build();
}
#Test
#DisplayName("Car Controller Test")
public void carControllerTest() {
try {
CarCustomRequest carCustomRequest = buildRequest();
ResultActions resultActions = mockMvc.perform(post("/custom/endpoint")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(carCustomRequest)));
MvcResult mvcResult = resultActions.andExpect(status().isOk()).andReturn();
assertTrue(mvcResult.getResponse().getStatus() == 200);
} catch (Exception e) {
fail("Error testing /custom/endpoint");
}
}
In order to #Autowire you need to load component classes that create the corresponding bean. To make tests faster you could define custom application context and load required beans only inter of using #SpringBootTest without params.
#SpringBootTest(classes = JacksonAutoConfiguration.class)
class ObjectMapperTest {
#Autowired
private ObjectMapper mapper;
#Test
void checkObjectMapper() {
assertNotNull(mapper);
}
}
I would not use #Mock in this case because it will be required to create stubs for required methods.
As an alternative, you could simply create a new instance of the ObjectMapper in test.
class ObjectMapperTest {
private ObjectMapper mapper = new ObjectMapper();
#Test
void checkObjectMapper() {
assertNotNull(mapper);
}
}
You could also register additional modules if required
objectMapper.registerModule(new JavaTimeModule());
I have a Spring application,
and I've created this test:
#RunWith(SpringRunner.class)
#SpringJUnitWebConfig(locations = {
"classpath:testDatabaseContext.xml",
"classpath:testServicesContext.xml",
"classpath:backoffice-servlet.xml"
})
public class UserControllerTests {
#Autowired
private MockMvc mockMvc;
#Before
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
..
}
but when I start the test I got this error:
rg.junit.runners.model.InvalidTestClassError: Invalid test class 'com.pastis.UserControllerTests':
1. Method setup() should be public
2. Method setup should have no parameters
Here an example:
RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = MyWebConfig.class)
public class CustomerControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup () {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
this.mockMvc = builder.build();
}
#Test
public void testUserController () throws Exception {
ResultMatcher ok = MockMvcResultMatchers.status()
.isOk();
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/customers");
this.mockMvc.perform(builder)
.andExpect(ok);
}
}
So I explain the output of the exception:
Add the modifier public to your method setup, otherwise JUnit can't invoke it
Remove the parameter from the method, #Before, #After and such don't allow parameters.
How to setup MockMvc correctly is another question. Recent Spring and additional annotations regarding web scope initialization and -behavior leave the scope of this answer. (This also requires more clarification, for example which JDK, which Spring or Spring Boot… XML configuration, dbunit and JUnit 4 suggest a legacy context.)
in my application-test.properties I have this server.servlet.context-path=/api
It works totally fine when I run the application and test it with postman. But as soon as I run my tests it swallows the part /api of the path.
So basically how it should be
localhost:8080/api/testUrl
but the controller is only available here
localhost:8080/testUrl
My Testclass head
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#AutoConfigureMockMvc
public class QaControllerIntegrationTest {
private static final String QA_URL = "/api";
#Autowired
private MockMvc mockMvc;
#MockBean
private QaService qaService;
#Autowired
private TestRestTemplate testRestTemplate;
no setup behavior implemented.
and tests (only for the sake of completeness - they would work if I remove the QA_URL)
#Test
void getQuestions() {
final ResponseEntity<List<QuestionAnswerDTO>> listResponseEntity = testRestTemplate.exchange(
QA_URL + "/questions", HttpMethod.GET, null, new ParameterizedTypeReference<>() {
});
assertThat(listResponseEntity.getStatusCode()).isEqualByComparingTo(HttpStatus.OK);
assertThat(listResponseEntity.getBody().get(0).getQuestion()).isEqualTo(QUESTION);
}
#Test
void addNewQa() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post(QA_URL + "/question")
.content(JacksonUtils.toString(questionAnswerDTO, false))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isCreated());
}
What do I miss here please?
Thank you =)
Because MockMvc isn't autoconfigured with context path and thus is unaware of it. If you want to include it, you can do:
MockMvcRequestBuilders.post(QA_URL + "/question").contextPath(QA_URL)
Notice prefix must match in order for Spring to figure out the remaining path. Typically a test shouldn't care about the context they are in therefore context path is never included.
I am new to spring boot.
I have a Service class as below
#Service
public class AService{
#Value("${sample.time})
private Long time;
...
public int sampleMethod(){
...
return time;
}
}
and my application.yml under src/resource has:
sample:
time:1
and my junit test class is:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = MainApplication.class)
public class AServiceTest{
#Autowired
private WebApplicationContext wac;
private Aservice aservice;
#Before
public void setupMockMvc() {
MockitoAnnotations.initMocks(this);
aservice = new AService();
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
public void sampleMethodTest(){
Assert.assertEquals(1,aservice.sampleMethod());
}
This always gives false, because the method aservice.sampleMethod() Returns 0.
It is not able to read the yml file under src when am testing.
I know that since ist injected with spring by standalone like this wont work. But it would be great help if you could guide me with possible Solutions or any pointers are helpful too.
Thanks in advance
Error is due to new AService(). remove new AService() and use autowired AService.
Try below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = MainApplication.class)
public class AServiceTest{
#Autowired
private WebApplicationContext wac;
#Autowired
private Aservice aservice;
#Before
public void setupMockMvc() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
public void sampleMethodTest(){
Assert.assertEquals(1,aservice.sampleMethod());
}
You need to make your .yaml file available under /src/test/resources too in order for tests to be able to access these properties.
This will also enable you to set test specific properties.
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();
}