Springboot API, Lombok, Junit Mockito Test
A mash of syntaxes to remember and reference….
Testing
Junit is typically used with other libraries like Mockito
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class ATest{
private DietPlanner dietPlanner;
@Mock GitFileDownloader gitFileDownloader;
@Captor private ArgumentCaptor<List<GitFileInfo>> gitFileArgumentCaptor;
@BeforeEach
void setup(){
this.dietPlanner = new DietPlanner();
}
@Test
void test(){
assertTrue(A.xx());
}
@Test
void testWithException(){
Executable e = () -> A.xx();
assertThrows(ArithmeticException.class, e);
}
@ParameterizedTest
@MethodSource("getArgumentsForCopyNewString")
void copyNewString(
final String sourceFileResource,
final String targetFileResource,
final String expectedOutputFileResource,
final String sourceMsgId)
throws Exception {
}
private static Stream<Arguments> getArgumentsForCopyNewString() {
return Stream.of(
Arguments.of(
"test-source-single-entry.po",
"test-target.po",
"expect-test-target-plus-single-entry.po",
"Archived"));
}
@Test
void testB(){
when(gitFileDownloader.downloadLocaleFile("123", "branch", "source-locale"))
.thenReturn(
DEFAULT_GIT_FILE_INFO
.toBuilder()
.filePath("sourceFile.po")
.file(tempSource)
.build());
}
@Test
void testWithArgumentCapture(){
// pretend gitFileUploader was initialised...
verify(gitFileUploader).uploadLocaleFiles(gitFileArgumentCaptor.capture());
final List<GitFileInfo> gitFileInfosOutput = gitFileArgumentCaptor.getValue();
assertThat(gitFileInfosOutput).hasSize(1);
}
@Test
void givenASpy_whenStubbingTheBehaviour_thenCorrect() {
List<String> list = new ArrayList<String>();
List<String> spyList = spy(list);
assertEquals(0, spyList.size());
doReturn(100).when(spyList).size();
assertThat(spyList).hasSize(100);
}
}
mocks: when(mock.method()).thenReturn()
doReturn().when(spy).method();
Mock VS Spy
stub means defining a behavior when some function is called. when(mockList.get(100)).thenReturn(expected);
- By default, calling the methods of mock object will do nothing (When there is no stub). If it is a method, it will not return anything…it returns null.
- Spy object will call the real method when not stub.
If you want to be safe and avoid calling external services and just want to test the logic inside of the unit, then use mock. If you want to call external service and perform calling of real dependency, or simply say, you want to run the program as it is and just stub specific methods, then use spy.
MockMVC
A JUnit test class to test the Spring MVC controller request and responses can use the below-given configuration.
@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRESTController.class)
public class TestEmployeeRESTController {
@Autowired
private MockMvc mvc;
@Test
public void getAllEmployeesAPI() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/employees")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.employees").exists())
.andExpect(MockMvcResultMatchers.jsonPath("$.employees[*].employeeId").isNotEmpty());
// I don't really like using MockMVCResultMatcher.jsonpath....
// I prefer to use andReturn();
}
@Test
public void getEmployeeByIdAPI() throws Exception {
mvc.perform( MockMvcRequestBuilders
.get("/employees/{id}", 1)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.employeeId").value(1));
}
@Test
public void createEmployeeAPI() throws Exception {
mvc.perform( MockMvcRequestBuilders
.post("/employees")
.content(asJsonString(new EmployeeVO(null, "firstName4", "lastName4", "email4@mail.com")))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(MockMvcResultMatchers.jsonPath("$.employeeId").exists());
}
public static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
void shouldReturn200WhenRequestToOutsourceTranslationToXtmIsReceived() throws Exception {
final String requestBody =
"{\n"
+ " \"ticketNumber\": \"PENGUIN-1234\",\n"
+ " \"projectManagerId\": \"12345\",\n"
+ " \"hasAutoStart\": \"true\"\n"
+ "}";
MvcResult result =
mockMvc.perform(postRequest(requestBody, "/toXtm"))
.andDo(print())
.andExpect(status().isOk())
.andReturn();
assertEquals(result.getResponse().getContentAsString(), "true");
}
private MockHttpServletRequestBuilder postRequest(final String content, final String endpoint) {
return post(BASE_URL + endpoint)
.contentType(APPLICATION_JSON_VALUE)
.content(content)
.characterEncoding("utf-8");
}
}
import aero.airlab.challenge.conflictforecast.api.Conflict;
import aero.airlab.challenge.conflictforecast.api.ConflictForecastRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.kotlin.KotlinModule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import src.ConflictController;
import src.ConflictService;
import java.io.File;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ConflictControllerTest {
private ConflictController conflictController;
@MockBean
private ConflictService conflictService;
@BeforeEach
void setUp() {
conflictService = new ConflictService();
conflictController = new ConflictController(this.conflictService);
}
@Test
public void testCheckConflict() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServerName("www.something.com");
request.setMethod("GET");
request.setRequestURI(
"/thales/conflict");
final ResponseEntity<List<Conflict>> response = conflictController.conflict(getRequestObjectFromJsonFile("demo-request-3.json"));
assertEquals(response.getStatusCode(), HttpStatus.OK);
}
private ConflictForecastRequest getRequestObjectFromJsonFile(String path) throws IOException {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new KotlinModule());
File file = new File(getClass().getClassLoader().getResource(path).getFile());
return objectMapper.readValue(file, ConflictForecastRequest.class);
}
}
Gradle imports
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.junit.jupiter:junit-jupiter-params'
// Tests
testCompile 'org.mockito:mockito-inline'
API
@GetMapping(value = "/employees")
public EmployeeListVO getAllEmployees(){
//code
}
@GetMapping(value = "/employees/{id}")
public ResponseEntity<EmployeeVO> getEmployeeById (@PathVariable("id") int id){
//code
}
@PostMapping(value = "/employees")
public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee){
//code
return new ResponseEntity<EmployeeVO>(employee, HttpStatus.CREATED);
}
DTO
@Data
@Builder
public class XtmJiraLink {
@Id private ObjectId id;
@Field("xtm_project")
private String xtmProjectId;
@Field("jira")
private String jira;
@Field("project_id")
private ObjectId projectId; // id in string-db.project
@CreatedDate
@Field("created_time")
private DateTime createdTime;
}
Others
@NoArgsConstructor: This annotation generates a no-argument constructor for a class. This is useful when you need to instantiate a bean without providing any arguments.
@AllArgsConstructor: This annotation generates a constructor with arguments for all of the fields in a class. This is useful when you need to instantiate a bean and provide values for all of its fields.
@RequiredArgsConstructor: This annotation generates a constructor with arguments for all of the final fields in a class. This is useful when you have a class with many final fields and want to enforce that they are initialized at construction time.
@Builder: This annotation generates a builder class that allows you to construct objects with a more readable and concise syntax. This is useful when you have many fields in a class and want to provide a flexible and intuitive way to construct instances.
@Value: This annotation generates an immutable class with a constructor for all of the fields. This is useful when you have a class with many fields that should not be modified after construction.
@Data: This annotation generates boilerplate code for getters, setters, equals, hashCode, and toString methods. This is useful when you have a class with many fields and want to avoid writing repetitive code.
Object Mapper
dependencies{
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
}
// We can also expose objectMapper as a bean in App Config
@Configuration
public class AppConfig{
@Bean
public ObjectMapper inputObjectMapper() {
return new ObjectMapper();
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
@Qualifier("inputObjectMapper")
private final ObjectMapper objectMapper;
public static void main(String[] args) {
// Object to JSON
MyObject myObject = new MyObject();
String json = objectMapper.writeValueAsString(myObject);
System.out.println(json);
// JSON to Object
String json = "{\"name\":\"John\",\"age\":30}";
MyObject myObject = objectMapper.readValue(json, MyObject.class);
System.out.println(myObject);
}
}
private final RestTemplate restTemplate;
ResponseEntity<byte[]> res =
restTemplate.exchange(
downloadPoFilesUrl,
HttpMethod.POST,
getRequestEntityForJson(request),
byte[].class);
if (res.getStatusCode() != HttpStatus.OK) {
throw new Exception(
String.format("resp status: {}, resp body: {}",
res.getStatusCode(),
res.getBody()));
}
/////
public <T> HttpEntity<T> getRequestEntityForJson(final T values) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
return new HttpEntity<>(values, headers);
}
To quickly create a new spring boot project using Intellij, follow this
https://www.jetbrains.com/help/idea/your-first-spring-application.html#run-spring-application