JavaScript Required

We're sorry, but we doesn't work properly without JavaScript enabled.

Unit testing Spring Boot controllers

Technology: Unit testing is one level of software testing where individual components of a software are tested. The purpose of the unit testing of a Java software development is to validate each unit performs as designed. A unit is the smallest testable component, like methods.

Spring Boot is spring framework module which provides Rapid Application Development feature to spring framework.

Project Setup:

Create a sample spring boot application from your favorite IDE or http://start.spring.io add web dependency.

unit-spring-boot-controller

Open pom.xml file and check for spring-boot-starter-test dependency, if not present add the below dependency.

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>

Now let`s create sample controller for testing:

@RestController public class HelloController { @GetMapping("/hello") public String sayHelloWorld() { return "Hello World"; } }

Testing the application:

If we run the application and navigate to http://localhost:8080 and we can confirm that it is working fine, but we need to write test cases for automation.

We are using Junit for or testcases, we can feel free to use any test framework like testing etc.

SmokeTesting:

In smoke testing we will make sure that our controller is loaded, and bean is created for it.

@SpringBootTest is one of the annotation to bootstrap the application using @SpringBootApplication application, or we can also provide the classname to bootstrap using classes attribute.

Spring MVC provide Junit runner which we will use for initializing the tests.

@RunWith(SpringRunner.class) @SpringBootTest(classes = SpringBootApplicationExample.class) public class SmokeHelloControllerTest { @Autowired private HelloController helloController; @Test public void testApplicationLload() { assertNotNull("HelloController is not loaded", helloController); } }

By default, Spring applicationContext is shared between tests, suppose if test class contains multiple test methods are present, applicationContext is shared between tests. We can control this cache using @DirtiesContext annotation.

Verifying the HTTP Request Response:

For verifying the web application, we will use random port for running the application (to avoid conflicts in other environments), using webEnvironment attribute, and same can be retrieved using @LocalPort annotation.

Spring test also provide TestRestTemplate for testing purpose.

@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = SpringBootApplicationExample.class) public class HTTPRequestTest { @LocalServerPort private int localPort; @Autowired private TestRestTemplate testRestTemplate; @Test public void testShouldReturnMessage() { assertThat(testRestTemplate.getForObject("http://localhost:" + localPort + "/hello", String.class) .contains("Hello World")); } }

Here, we are loading the all application related files, and starting the server, this may be overhead for unit testing, there are other ways to test only spring MVC controller.

@RunWith(SpringRunner.class) @WebMvcTest public class WebTest { @Autowired private MockMvc mockMvc; @Test public void testShouldReturnMessage() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/hello")).andExpect(MockMvcResultMatchers.status().is(200)) .andExpect(MockMvcResultMatchers.content().string("Hello World")) .andExpect(MockMvcResultMatchers.header().string("Content-Type", "text/plain;charset=UTF-8")) .andExpect(MockMvcResultMatchers.header().string("Content-Length", "11")); } }

But there is way to test only our controller instead of loading the all controller using controllers attribute of @WebMvcTest annotation, this will reduce the execution time.

@RunWith(SpringRunner.class) @WebMvcTest(controllers = HelloController.class) public class HelloWorldTest { @Autowired private MockMvc mockMvc; @Test public void testShouldReturnMessage() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/hello")).andExpect(MockMvcResultMatchers.status().is(200)) .andExpect(MockMvcResultMatchers.content().string("Hello World")) .andExpect(MockMvcResultMatchers.header().string("Content-Type", "text/plain;charset=UTF-8")) .andExpect(MockMvcResultMatchers.header().string("Content-Length", "11")); } }

There are some more useful annotations are present for mocking spring beans, @MockBean and @SpyBean.

But there will be a difference between two.

@MockBean will give the reference to object, it doesn`t related to underlying object, all the methods and fields in underlying object will not present in mock object.

@SpyBean will give the reference to an object which wraps the underlying object, method calls to object either we can have intercepted, or we can call the underlying object method. Only selected methods will be mocked, other method will call original methods as usual.

@TestPropertySource is the annotation used to load the properties along with application source properties file.

Using SpringBoot @MockBean:

Create HelloService service class and below content to it.

@Service public class HelloService { public String sayHello() { return "Hello World"; } }

And update HelloController to inject service class into it.

@RestController public class HelloController { @Autowired private HelloService service; @GetMapping("/hello") public String sayHelloWorld() { return service.sayHello(); } }

After changing the source, we need to update the update test classes as well.

For testing controller, we will mock the service class using @MockBean annotation.

Update HelloWorldTest class with below content:

@RunWith(SpringRunner.class) @WebMvcTest(controllers = HelloController.class) public class HelloWorldTest { @Autowired private MockMvc mockMvc; @MockBean private HelloService service; @Test public void testShouldReturnMessage() throws Exception { when(service.sayHello()).thenReturn("Hello World"); mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.ALL)) .andExpect(MockMvcResultMatchers.status().is(200)) .andExpect(MockMvcResultMatchers.content().string("Hello World")) .andExpect(MockMvcResultMatchers.header().string("Content-Type", "text/plain;charset=UTF-8")) .andExpect(MockMvcResultMatchers.header().string("Content-Length", "11")); } }

Here we used to @MockBean to mock and before we perform controller test, we will provide expectations of all the methods which are invoked from controller.

In our controller we used sayHello() method, so we are mocking and providing the excpectation, when this method is invoked from controller.

when(service.sayHello()).thenReturn("Hello World");

Conclusion:

we learned how to write tests using spring-boot-starter-test library, and various annotations are used inside the unit tests.

Source code of the above content will available at https://github.com/sravan4rmhyd/spring-boot-test-web.git

Ast Note

Some of our clients

team