SpringBoot - Unit Testing

SpringBoot – Unit Testing with Mockito

https://frontbackend.com/spring-boot/spring-boot-2-junit-5-mockito

  • Application - the main Spring Boot application class used for starting web container,

  • TestReportController - Spring Rest Controller for testing purposes,

  • TestReportRepository - Spring Service used to check how autowire works in tests,

  • TestReportControllerMockitoTest - test for TestReportControllerusing Mockito,

  • TestReportControllerMockMvcTest - test for TestReportControllerusing MockMvc,

  • TestReportCon*RestTemplTest - test for TestReportController using TestRestTemplate.

Controller Class

@RestController
public class TestReportController {
	@Autowired
	TestReportRepository repository;
	
	@Autowired
	RestTemplate restTemplate;		  
	
	@GetMapping("/testReport/{id}")
	public ResponseEntity<TestReport> getReportByID(@PathVariable int id){		
		TestReport data = repository.getById(id); 		 
			return ResponseEntity
		        .ok()
		        .contentType(MediaType.APPLICATION_JSON)
		        .body(data);		 
	}
	
	
	@GetMapping(value = "/testReport/all", produces = MediaType.APPLICATION_JSON_VALUE)
	public ResponseEntity<List<TestReport>> getAllTestReports() {
			List<TestReport> reports = new ArrayList<TestReport>();
			repository.findAll().forEach(reports::add);
			if (reports.isEmpty()) {
				return new ResponseEntity<>(HttpStatus.NO_CONTENT);
			}
			return new ResponseEntity<>(reports, HttpStatus.OK);		 
	}

	@GetMapping(value =  "/testReport/mi", produces = "application/json")
	public ResponseEntity<Object> getAllMIReportsNoramlcall() {
 	ResponseEntity<Object> responseEntity =  restTemplate.getForEntity("http://MI-MICROSERVICE/mi/anthology/all", Object.class); 
			return new ResponseEntity<>(responseEntity, HttpStatus.OK);		 
	}
}

Using MockMvc -

We used the MockMvc class and @AutoConfigureMockMvc that will configure it and inject it into the tested class. The MockMvc class is used to perform API calls, but instead of doing HTTP requests, Spring will test only the implementation that handle them in TestReportController

@SpringBootTest
@AutoConfigureMockMvc
public class TestReportControllerMockMvcTest {
	
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void getReportByIDTest() throws Exception{              
        this.mockMvc.perform(get("/testReport/1"))
        .andDo(print())
        .andExpect(status().isOk())
        .andExpect(content().string(containsString("Bllod Report")));
    }
    
    @Test
    public void getAllResportsTest() throws Exception{       	
        this.mockMvc.perform(get("/testReport/all"))
        .andDo(print())
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON));
    }

}

Using TestRestTemplate

Spring boot test that makes use of TestRestTemplate to call REST API. In this approach, we can use @Autowired annotation just like in runtime applications. Spring will interpret them and do the necessary injections. In @SpringBootTest tests real Spring Boot application server is being started.

In the test we used:

  • webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT - to start the server with a random port in order to avoid any port conflicts,

  • @LocalServerPort - this annotation tells Spring to inject a random port to the specific field,

  • TestRestTemplate - RestTemplate for tests used to make a real HTTP requests.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestReportControllerRestTemplateTest 	
	    @LocalServerPort
	    private int port;
	    private String url;

	    @Autowired
	    private TestRestTemplate restTemplate;

	    @BeforeEach
	    public void setUp() {
	        url = String.format("http://localhost:%d/", port);
	    }
	    
	    @Test
	    public void greetingShouldReturnDefaultMessage() {
	        assertThat(this.restTemplate.getForObject(url+"testreport/1", String.class)).contains("Report");
	    }
}

Using Mockito unit test

  • @Mock creates a mock.

  • @InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock (or @Spy) annotations into this instance.

In above controller class has two dependent objects. First, we need to mock those objects & then we need to inject them to main controller class

	@Autowired
	TestReportRepository repository;
	
	@Autowired
	RestTemplate restTemplate;	
@ExtendWith(MockitoExtension.class)
public class TestReportControllerTest {
	
	@Mock
	TestReportRepository repository;
	
	@Mock
	RestTemplate restTemplate;	
	
	@InjectMocks
	TestReportController controller;
	
	
	@BeforeEach
    void setMockOutput() {
        when(repository.getById(1)).thenReturn(new TestReport("Report", "", "", "", ""));
    }
	
	
	@Test
   public void getReportByIDTest() throws Exception{              
  assertThat(controller.getReportByID(1).getBody().getReportName().equalsIgnoreCase("Report"));
    }

}

If any datasource object is their, just mock annotation will automatically creates Connection object with test properties. Try adding your datasource as a @MockBean too:

@MockBean
private DataSource dataSource