82

I have a sample Spring Boot app with the following

Boot main class

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

Controller

@RestController
@EnableAutoConfiguration
public class HelloWorld {
    @RequestMapping("/")
    String gethelloWorld() {
        return "Hello World!";
    }

}

What's the easiest way to write a unit test for the controller? I tried the following but it complains about failing to autowire WebApplicationContext

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = DemoApplication.class)
public class DemoApplicationTests {

    final String BASE_URL = "http://localhost:8080/";

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void testSayHelloWorld() throws Exception{

         this.mockMvc.perform(get("/")
                 .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
                 .andExpect(status().isOk())
                 .andExpect(content().contentType("application/json"));
    }

    @Test
    public void contextLoads() {
    }

}
bogdan.rusu
  • 901
  • 4
  • 21
  • 41
user6123723
  • 10,546
  • 18
  • 67
  • 109
  • 3
    Try annotating `DemoApplication` with `@WebAppConfiguration`. If that doesn't work, could you add the code of it too? – Augusto Mar 14 '15 at 20:51
  • years later... I think if you use mockMVC its not unit test, its integration test – gurkan May 26 '23 at 21:20

5 Answers5

53

Spring MVC offers a standaloneSetup that supports testing relatively simple controllers, without the need of context.

Build a MockMvc by registering one or more @Controller's instances and configuring Spring MVC infrastructure programmatically. This allows full control over the instantiation and initialization of controllers, and their dependencies, similar to plain unit tests while also making it possible to test one controller at a time.

An example test for your controller can be something as simple as

public class DemoApplicationTests {

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorld()).build();
    }

    @Test
    public void testSayHelloWorld() throws Exception {
        this.mockMvc.perform(get("/")
           .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
           .andExpect(status().isOk())
           .andExpect(content().contentType("application/json"));

    }
}
Felix Schildmann
  • 574
  • 1
  • 7
  • 22
Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • what does this test ? II have tried it with when(employeeService.getById(1)).thenReturn(employee); If i certify the return, how can I be sure it is working as expected ? – Dimitri Kopriwa Aug 19 '16 at 09:03
  • the example just discusses basic expectations, but you can drill down further using Hamcrest matchers, and assert anything in the body as well e.g. actual json response by what you expect. Basically, whatever you expect to happen with the Employee instance, you can assert as well, be it transformation to JSON or XML, renaming of properties etc. – Master Slave Aug 19 '16 at 09:55
  • .andExpect(content().contentType("application/json")); this line is giving me an error "The method content() is undefined for the type". – JayC Apr 18 '17 at 18:03
  • 4
    You need some static imports for the example code above. For `content()` that would be `org.springframework.test.web.servlet.result.MockMvcResultMatchers.content`. By the way: I'd recommend using MediaType constants like `MediaType.APPLICATION_JSON` and `MediaType.APPLICATION_JSON_UTF8` instead of the String-based values used in the example. – Nils Breunese May 15 '17 at 16:13
32

The new testing improvements that debuted in Spring Boot 1.4.M2 can help reduce the amount of code you need to write situation such as these.

The test would look like so:

import static org.springframework.test.web.servlet.request.MockMvcRequestB‌​uilders.get; 
import static org.springframework.test.web.servlet.result.MockMvcResultMat‌​chers.content; 
import static org.springframework.test.web.servlet.result.MockMvcResultMat‌​chers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(HelloWorld.class)
public class UserVehicleControllerTests {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    public void testSayHelloWorld() throws Exception {
        this.mockMvc.perform(get("/").accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
                .andExpect(status().isOk())
                .andExpect(content().contentType("application/json"));
    }
    
}

See this blog post for more details as well as the documentation

eis
  • 51,991
  • 13
  • 150
  • 199
geoand
  • 60,071
  • 24
  • 172
  • 190
  • 7
    This does not work. You will still get a ``java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test`` error. – Lucas Oct 11 '16 at 15:49
  • 1
    This works greate with `spring-boot 1.4.0` and `junit`. – Eric Oct 22 '16 at 10:00
  • I also have not found any troubles... I would be great to a have a reproducible case of the error – geoand Oct 22 '16 at 14:37
  • 1
    @Lucas The error you get is due to the algorithm that spring-boot use to search for context initializer, you need an application class in the package of testing class, or its sub package, which is marked by `@SpringBootApplication` or `@SpringBootConfiguration`, refer to: https://github.com/spring-projects/spring-boot/issues/5987 – Eric Oct 25 '16 at 08:01
  • This worked when I moved the test class to a package that contained a ``SpringBootApplication`` I attempted to get this working without one but it seemed to be more trouble than it was worth. – Lucas Oct 25 '16 at 18:00
  • how to fix ".andExpect(content().contentType("application/json"));" and receiving "The method content() is undefined for the type" – JayC Apr 18 '17 at 18:05
  • 3
    would be nice to add the static imports: import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; – tibi Nov 11 '17 at 16:14
  • @tibi Great suggestion! Thanks! – geoand Nov 12 '17 at 15:43
  • I got a problem with this one when using spring boot and sping data. The repositories won't initialize and therefore would return error messages. using @SpringBootTest as sugested by Siva Kumar's answer worked fine however – Xtroce May 08 '18 at 15:28
  • The only reason I don't like to use this solution is because here you have to create a spring context, and load up the whole application. Ideally when using unit tests, they should not have to load up a context in order to run – carl saptarshi Aug 01 '19 at 07:59
7

Here is another answer using Spring MVC's standaloneSetup. Using this way you can either autowire the controller class or Mock it.

    import static org.mockito.Mockito.mock;
    import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.web.server.MockMvc;
    import org.springframework.test.web.server.setup.MockMvcBuilders;


    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class DemoApplicationTests {

        final String BASE_URL = "http://localhost:8080/";

        @Autowired
        private HelloWorld controllerToTest;

        private MockMvc mockMvc;

        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders.standaloneSetup(controllerToTest).build();
        }

        @Test
        public void testSayHelloWorld() throws Exception{
            //Mocking Controller
            controllerToTest = mock(HelloWorld.class);

             this.mockMvc.perform(get("/")
                     .accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
                     .andExpect(status().isOk())
                     .andExpect(content().mimeType(MediaType.APPLICATION_JSON));
        }

        @Test
        public void contextLoads() {
        }

    }
Siva Kumar
  • 560
  • 6
  • 11
  • There are also @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) avaiable. – petertc Apr 24 '17 at 09:57
  • This works for me when I have an autowired service field in the controller, I've tried using geoand's way, but the service field always be null, don't know why? – Eason Jun 07 '17 at 11:29
  • 1
    This starts the entire context though and this is also an integration test, not a unit test. – Michael Mar 15 '18 at 19:52
5

Adding @WebAppConfiguration (org.springframework.test.context.web.WebAppConfiguration) annotation to your DemoApplicationTests class will work.

Amir Azizkhani
  • 1,662
  • 17
  • 30
Charles Wang
  • 267
  • 1
  • 4
  • 9
0

Let assume i am having a RestController with GET/POST/PUT/DELETE operations and i have to write unit test using spring boot.I will just share code of RestController class and respective unit test.Wont be sharing any other related code to the controller ,can have assumption on that.

@RestController
@RequestMapping(value = “/myapi/myApp” , produces = {"application/json"})
public class AppController {


    @Autowired
    private AppService service;

    @GetMapping
    public MyAppResponse<AppEntity> get() throws Exception {

        MyAppResponse<AppEntity> response = new MyAppResponse<AppEntity>();
        service.getApp().stream().forEach(x -> response.addData(x));    
        return response;
    }


    @PostMapping
    public ResponseEntity<HttpStatus> create(@RequestBody AppRequest request) throws Exception {
        //Validation code       
        service.createApp(request);

        return ResponseEntity.ok(HttpStatus.OK);
    }

    @PutMapping
    public ResponseEntity<HttpStatus> update(@RequestBody IDMSRequest request) throws Exception {

        //Validation code
        service.updateApp(request);

        return ResponseEntity.ok(HttpStatus.OK);
    }

    @DeleteMapping
    public ResponseEntity<HttpStatus> delete(@RequestBody AppRequest request) throws Exception {

        //Validation        
        service.deleteApp(request.id);

        return ResponseEntity.ok(HttpStatus.OK);
    }

}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Main.class)
@WebAppConfiguration
public abstract class BaseTest {
   protected MockMvc mvc;
   @Autowired
   WebApplicationContext webApplicationContext;

   protected void setUp() {
      mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
   }
   protected String mapToJson(Object obj) throws JsonProcessingException {
      ObjectMapper objectMapper = new ObjectMapper();
      return objectMapper.writeValueAsString(obj);
   }
   protected <T> T mapFromJson(String json, Class<T> clazz)
      throws JsonParseException, JsonMappingException, IOException {

      ObjectMapper objectMapper = new ObjectMapper();
      return objectMapper.readValue(json, clazz);
   }
}

public class AppControllerTest extends BaseTest {

    @MockBean
    private IIdmsService service;

    private static final String URI = "/myapi/myApp";


   @Override
   @Before
   public void setUp() {
      super.setUp();
   }

   @Test
   public void testGet() throws Exception {
       AppEntity entity = new AppEntity();
      List<AppEntity> dataList = new ArrayList<AppEntity>();
      AppResponse<AppEntity> dataResponse = new AppResponse<AppEntity>();
      entity.setId(1);
      entity.setCreated_at("2020-02-21 17:01:38.717863");
      entity.setCreated_by(“Abhinav Kr”);
      entity.setModified_at("2020-02-24 17:01:38.717863");
      entity.setModified_by(“Jyoti”);
            dataList.add(entity);

      dataResponse.setData(dataList);

      Mockito.when(service.getApp()).thenReturn(dataList);

      RequestBuilder requestBuilder =  MockMvcRequestBuilders.get(URI)
                 .accept(MediaType.APPLICATION_JSON);

        MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();

        String expectedJson = this.mapToJson(dataResponse);
        String outputInJson = mvcResult.getResponse().getContentAsString();

        assertEquals(HttpStatus.OK.value(), response.getStatus());
        assertEquals(expectedJson, outputInJson);
   }

   @Test
   public void testCreate() throws Exception {

       AppRequest request = new AppRequest();
       request.createdBy = 1;
       request.AppFullName = “My App”;
       request.appTimezone = “India”;

       String inputInJson = this.mapToJson(request);
       Mockito.doNothing().when(service).createApp(Mockito.any(AppRequest.class));
       service.createApp(request);
       Mockito.verify(service, Mockito.times(1)).createApp(request);

       RequestBuilder requestBuilder =  MockMvcRequestBuilders.post(URI)
                                                             .accept(MediaType.APPLICATION_JSON).content(inputInJson)
                                                             .contentType(MediaType.APPLICATION_JSON);

       MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
       MockHttpServletResponse response = mvcResult.getResponse();
       assertEquals(HttpStatus.OK.value(), response.getStatus());

   }

   @Test
   public void testUpdate() throws Exception {

       AppRequest request = new AppRequest();
       request.id = 1;
       request.modifiedBy = 1;
        request.AppFullName = “My App”;
       request.appTimezone = “Bharat”;

       String inputInJson = this.mapToJson(request);
       Mockito.doNothing().when(service).updateApp(Mockito.any(AppRequest.class));
       service.updateApp(request);
       Mockito.verify(service, Mockito.times(1)).updateApp(request);

       RequestBuilder requestBuilder =  MockMvcRequestBuilders.put(URI)
                                                             .accept(MediaType.APPLICATION_JSON).content(inputInJson)
                                                             .contentType(MediaType.APPLICATION_JSON);

       MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
       MockHttpServletResponse response = mvcResult.getResponse();
       assertEquals(HttpStatus.OK.value(), response.getStatus());

   }

   @Test
   public void testDelete() throws Exception {

       AppRequest request = new AppRequest();
       request.id = 1;

       String inputInJson = this.mapToJson(request);
       Mockito.doNothing().when(service).deleteApp(Mockito.any(Integer.class));
       service.deleteApp(request.id);
       Mockito.verify(service, Mockito.times(1)).deleteApp(request.id);

       RequestBuilder requestBuilder =  MockMvcRequestBuilders.delete(URI)
                                                             .accept(MediaType.APPLICATION_JSON).content(inputInJson)
                                                             .contentType(MediaType.APPLICATION_JSON);

       MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
       MockHttpServletResponse response = mvcResult.getResponse();
       assertEquals(HttpStatus.OK.value(), response.getStatus());

   }

}
abhinav kumar
  • 1,487
  • 1
  • 12
  • 20