* BAEL-7066: Happy path test

* BAEL-7066: Fix the test

* BAEL-7066: Change assume to assert

* BAEL-7066: Ignore Nulls test

* BAEL-7066: Combined tests

* BAEL-7066: Fix a typo and removed smoke tests

* BAEL-7066: Fix formatting

* BAEL-7066: Renamed to Employees

* BAEL-7066: Extract a base test class

* BAEL-7066: Added a skeleton for non absent test

* BAEL-7066: Added absent employee tests

* BAEL-7066: Fix formatting

* BAEL-7066: Empty values test

* BAEL-7066: Moved controllers

* BAEL-7066: Renamed methods

* BAEL-7066: Refactor nonNull tests

* BAEL-7066: Refactor nonEmpty tests

* BAEL-7066: Refactor nonAbsent tests

* BAEL-7066: Renamed the base test

* BAEL-7066: cleanup

* BAEL-7066: Fixed indents
This commit is contained in:
Eugene Kovko 2024-01-01 23:00:36 +01:00 committed by GitHub
parent 2096eac575
commit c606fc82f4
14 changed files with 674 additions and 0 deletions

View File

@ -0,0 +1,8 @@
package com.baeldung.jsonignore;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
}

View File

@ -0,0 +1,67 @@
package com.baeldung.jsonignore.absentfields;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.util.Optional;
@JsonInclude(Include.NON_ABSENT)
public class Employee {
private String lastName;
private String firstName;
private long id;
private Optional<Salary> salary;
public Employee() {
}
public Employee(final long id, final String lastName, final String firstName, final Optional<Salary> salary) {
this.id = id;
this.lastName = lastName;
this.firstName = firstName;
this.salary = salary;
}
public String getLastName() {
return lastName;
}
public String getFirstName() {
return firstName;
}
public long getId() {
return id;
}
public void setLastName(final String lastName) {
this.lastName = lastName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public void setId(final long id) {
this.id = id;
}
public Optional<Salary> getSalary() {
return salary;
}
public void setSalary(final Optional<Salary> salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"lastName='" + lastName + '\'' +
", firstName='" + firstName + '\'' +
", id=" + id +
", salary=" + salary +
'}';
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.jsonignore.absentfields;
import java.math.BigDecimal;
public class Salary {
private BigDecimal hourlyRate;
public Salary() {
}
public Salary(BigDecimal hourlyRate) {
this.hourlyRate = hourlyRate;
}
public BigDecimal getHourlyRate() {
return hourlyRate;
}
public void setHourlyRate(final BigDecimal hourlyRate) {
this.hourlyRate = hourlyRate;
}
@Override
public String toString() {
return "Salary{" +
"hourlyRate=" + hourlyRate +
'}';
}
}

View File

@ -0,0 +1,19 @@
package com.baeldung.jsonignore.controller;
import com.baeldung.jsonignore.absentfields.Employee;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(AbsentEmployeeEchoController.USERS)
public class AbsentEmployeeEchoController {
public static final String USERS = "/absent_employees";
@PostMapping
public Employee echoUser(@RequestBody Employee employee) {
return employee;
}
}

View File

@ -0,0 +1,19 @@
package com.baeldung.jsonignore.controller;
import com.baeldung.jsonignore.nullfields.Employee;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(EmployeeEchoController.USERS)
public class EmployeeEchoController {
public static final String USERS = "/employees";
@PostMapping
public Employee echoUser(@RequestBody Employee employee) {
return employee;
}
}

View File

@ -0,0 +1,19 @@
package com.baeldung.jsonignore.controller;
import com.baeldung.jsonignore.emptyfields.Employee;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(EmptyEmployeeEchoController.USERS)
public class EmptyEmployeeEchoController {
public static final String USERS = "/empty_employees";
@PostMapping
public Employee echoUser(@RequestBody Employee employee) {
return employee;
}
}

View File

@ -0,0 +1,93 @@
package com.baeldung.jsonignore.emptyfields;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@JsonInclude(Include.NON_EMPTY)
public class Employee {
private String lastName;
private String firstName;
private long id;
private Optional<Salary> salary;
private String phoneticName = "";
private List<PhoneNumber> phoneNumbers = new ArrayList<>();
public Employee() {
}
public Employee(final long id, final String lastName, final String firstName,
final Optional<Salary> salary, final String phoneticName, final List<PhoneNumber> phoneNumbers) {
this.id = id;
this.lastName = lastName;
this.firstName = firstName;
this.salary = salary;
this.phoneticName = phoneticName != null ? phoneticName : "";
this.phoneNumbers.addAll(phoneNumbers);
}
public String getLastName() {
return lastName;
}
public void setLastName(final String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
public Optional<Salary> getSalary() {
return salary;
}
public void setSalary(final Optional<Salary> salary) {
this.salary = salary;
}
public String getPhoneticName() {
return phoneticName;
}
public void setPhoneticName(final String phoneticName) {
this.phoneticName = phoneticName;
}
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
public void setPhoneNumbers(final List<PhoneNumber> phoneNumbers) {
this.phoneNumbers = phoneNumbers;
}
@Override
public String toString() {
return "Employee{" +
"lastName='" + lastName + '\'' +
", firstName='" + firstName + '\'' +
", id=" + id +
", salary=" + salary +
", phoneticName='" + phoneticName + '\'' +
", phoneNumbers=" + phoneNumbers +
'}';
}
}

View File

@ -0,0 +1,28 @@
package com.baeldung.jsonignore.emptyfields;
public class PhoneNumber {
private String number;
public PhoneNumber() {
}
public PhoneNumber(final String number) {
this.number = number;
}
public String getNumber() {
return number;
}
public void setNumber(final String number) {
this.number = number;
}
@Override
public String toString() {
return "PhoneNumber{" +
"number='" + number + '\'' +
'}';
}
}

View File

@ -0,0 +1,30 @@
package com.baeldung.jsonignore.emptyfields;
import java.math.BigDecimal;
public class Salary {
private BigDecimal hourlyRate;
public Salary() {
}
public Salary(BigDecimal hourlyRate) {
this.hourlyRate = hourlyRate;
}
public BigDecimal getHourlyRate() {
return hourlyRate;
}
public void setHourlyRate(final BigDecimal hourlyRate) {
this.hourlyRate = hourlyRate;
}
@Override
public String toString() {
return "Salary{" +
"hourlyRate=" + hourlyRate +
'}';
}
}

View File

@ -0,0 +1,83 @@
package com.baeldung.jsonignore.nullfields;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.util.Objects;
@JsonInclude(Include.NON_NULL)
public class Employee {
private String lastName;
private String firstName;
private long id;
public Employee() {
}
public Employee(final long id, final String firstName, final String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
public String getFirstName() {
return firstName;
}
public long getId() {
return id;
}
public void setLastName(final String lastName) {
this.lastName = lastName;
}
public void setFirstName(final String firstName) {
this.firstName = firstName;
}
public void setId(final long id) {
this.id = id;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Employee employee = (Employee) o;
if (id != employee.id) {
return false;
}
if (!Objects.equals(lastName, employee.lastName)) {
return false;
}
return Objects.equals(firstName, employee.firstName);
}
@Override
public int hashCode() {
int result = lastName != null ? lastName.hashCode() : 0;
result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
result = 31 * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public String toString() {
return "User{" +
"lastName='" + lastName + '\'' +
", firstName='" + firstName + '\'' +
", id=" + id +
'}';
}
}

View File

@ -0,0 +1,123 @@
package com.baeldung.jsonignore;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.baeldung.jsonignore.controller.AbsentEmployeeEchoController;
import com.baeldung.jsonignore.controller.EmployeeEchoController;
import com.baeldung.jsonignore.controller.EmptyEmployeeEchoController;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@WebMvcTest(controllers = {EmployeeEchoController.class, AbsentEmployeeEchoController.class,
EmptyEmployeeEchoController.class})
abstract class AbstractEmployeeEchoControllerBaseIntegrationTest {
@Autowired
protected ObjectMapper mapper = new ObjectMapper();
@Autowired
protected MockMvc mockMvc;
protected <T> MvcResult sendRequestAndGetResult(final T expected, final String endpoint) throws Exception {
final String payload = mapper.writeValueAsString(expected);
return mockMvc.perform(post(endpoint)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(payload))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
}
protected static void nonNullFieldsShouldNonBeMissing(final List<String> nonNullFields, final JsonNode jsonNode) {
nonNullFields.forEach(nullField -> {
final JsonNode nameNode = jsonNode.path(nullField);
assertThat(nameNode.isMissingNode()).isFalse();
});
}
protected static void nullFieldsShouldBeMissing(final List<String> nullFields, final JsonNode jsonNode) {
nullFields.forEach(nullField -> {
final JsonNode nameNode = jsonNode.path(nullField);
assertThat(nameNode.isMissingNode()).isTrue();
});
}
protected static <T> List<String> filterFieldsAndGetNames(final T object, final Predicate<Field> predicate) {
return ReflectionUtils.findFields(object.getClass(), predicate, HierarchyTraversalMode.BOTTOM_UP)
.stream().map(Field::getName).collect(Collectors.toList());
}
protected static <T> boolean isFieldNull(final T object, final Field s) {
try {
if (Object.class.isAssignableFrom(s.getType())) {
s.setAccessible(true);
return s.get(object) == null;
} else {
return false;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected static <T> boolean isFieldAbsent(final T object, final Field s) {
try {
if (Optional.class.isAssignableFrom(s.getType())) {
s.setAccessible(true);
final Optional<?> optional = ((Optional<?>) s.get(object));
if (optional != null) {
return !optional.isPresent();
} else {
return false;
}
} else {
return false;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
protected static <T> boolean isFieldEmpty(final T object, final Field s) {
try {
if (String.class.isAssignableFrom(s.getType())) {
s.setAccessible(true);
final String string = ((String) s.get(object));
if (string != null) {
return string.isEmpty();
} else {
return false;
}
} else if (Collection.class.isAssignableFrom(s.getType())) {
s.setAccessible(true);
final Collection collection = ((Collection) s.get(object));
if (collection != null) {
return collection.isEmpty();
} else {
return false;
}
} else {
return false;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,50 @@
package com.baeldung.jsonignore;
import static com.baeldung.jsonignore.controller.AbsentEmployeeEchoController.USERS;
import com.baeldung.jsonignore.absentfields.Employee;
import com.baeldung.jsonignore.absentfields.Salary;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.test.web.servlet.MvcResult;
class NonAbsentEmployeeFieldsEchoControllerIntegrationTest extends AbstractEmployeeEchoControllerBaseIntegrationTest {
@ParameterizedTest
@MethodSource
void giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringAbsentValues(final Employee expected) throws Exception {
final MvcResult result = sendRequestAndGetResult(expected, USERS);
final String response = result.getResponse().getContentAsString();
validateJsonFields(expected, response);
}
private void validateJsonFields(final Employee expected, final String response) throws JsonProcessingException {
final JsonNode jsonNode = mapper.readTree(response);
final Predicate<Field> nullField = s -> isFieldNull(expected, s);
final Predicate<Field> absentField = s -> isFieldAbsent(expected, s);
List<String> nullOrAbsentFields = filterFieldsAndGetNames(expected, nullField.or(absentField));
List<String> nonNullAndNonAbsentFields = filterFieldsAndGetNames(expected, nullField.negate().and(absentField.negate()));
nullFieldsShouldBeMissing(nullOrAbsentFields, jsonNode);
nonNullFieldsShouldNonBeMissing(nonNullAndNonAbsentFields, jsonNode);
}
static Stream<Arguments> giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringAbsentValues() {
final Salary baseSalary = new Salary(BigDecimal.TEN);
return Stream.of(
Arguments.of(new Employee(1L, "John", "Doe", Optional.empty())),
Arguments.of(new Employee(1L, null, "Doe", Optional.of(baseSalary))),
Arguments.of(new Employee(1L, "John", null, Optional.empty())),
Arguments.of(new Employee(1L, null, null, Optional.of(baseSalary)))
);
}
}

View File

@ -0,0 +1,60 @@
package com.baeldung.jsonignore;
import static com.baeldung.jsonignore.controller.EmptyEmployeeEchoController.USERS;
import com.baeldung.jsonignore.emptyfields.Employee;
import com.baeldung.jsonignore.emptyfields.PhoneNumber;
import com.baeldung.jsonignore.emptyfields.Salary;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.test.web.servlet.MvcResult;
class NonEmptyEmployeeFieldsEchoControllerIntegrationTest extends AbstractEmployeeEchoControllerBaseIntegrationTest {
@ParameterizedTest
@MethodSource
void giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringEmptyValues(final Employee expected) throws Exception {
final MvcResult result = sendRequestAndGetResult(expected, USERS);
final String response = result.getResponse().getContentAsString();
validateJsonFields(expected, response);
}
private void validateJsonFields(final Employee expected, final String response) throws JsonProcessingException {
final JsonNode jsonNode = mapper.readTree(response);
final Predicate<Field> nullField = s -> isFieldNull(expected, s);
final Predicate<Field> absentField = s -> isFieldAbsent(expected, s);
final Predicate<Field> emptyField = s -> isFieldEmpty(expected, s);
List<String> nullOrAbsentOrEmptyFields = filterFieldsAndGetNames(expected, nullField.or(absentField).or(emptyField));
List<String> nonNullAndNonAbsentAndNonEmptyFields = filterFieldsAndGetNames(expected,
nullField.negate().and(absentField.negate().and(emptyField.negate())));
nullFieldsShouldBeMissing(nullOrAbsentOrEmptyFields, jsonNode);
nonNullFieldsShouldNonBeMissing(nonNullAndNonAbsentAndNonEmptyFields, jsonNode);
}
static Stream<Arguments> giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringEmptyValues() {
final Salary baseSalary = new Salary(BigDecimal.TEN);
final List<PhoneNumber> phones = Arrays.asList(new PhoneNumber("123-456"), new PhoneNumber("789-012"));
return Stream.of(
Arguments.of(new Employee(1L, "John", "Doe", Optional.empty(), "ʤɒn dəʊ", new ArrayList<>())),
Arguments.of(new Employee(1L, null, "Doe", Optional.of(baseSalary), "dəʊ", new ArrayList<>())),
Arguments.of(new Employee(1L, "John", null, Optional.empty(), "ʤɒn", new ArrayList<>())),
Arguments.of(new Employee(1L, null, null, Optional.of(baseSalary), null, new ArrayList<>())),
Arguments.of(new Employee(1L, "John", "Doe", Optional.empty(), "ʤɒn dəʊ", phones)),
Arguments.of(new Employee(1L, null, "Doe", Optional.of(baseSalary), "dəʊ", phones)),
Arguments.of(new Employee(1L, "John", null, Optional.empty(), "ʤɒn", phones)),
Arguments.of(new Employee(1L, null, null, Optional.of(baseSalary), null, phones))
);
}
}

View File

@ -0,0 +1,45 @@
package com.baeldung.jsonignore;
import static com.baeldung.jsonignore.controller.EmployeeEchoController.USERS;
import com.baeldung.jsonignore.nullfields.Employee;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.test.web.servlet.MvcResult;
class NonNullEmployeeFieldsEchoControllerIntegrationTest extends AbstractEmployeeEchoControllerBaseIntegrationTest {
@ParameterizedTest
@MethodSource
void giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringNullValues(final Employee expected) throws Exception {
final MvcResult result = sendRequestAndGetResult(expected, USERS);
final String response = result.getResponse().getContentAsString();
validateJsonFields(expected, response);
}
private void validateJsonFields(final Employee expected, final String response) throws JsonProcessingException {
final JsonNode jsonNode = mapper.readTree(response);
final Predicate<Field> nullField = s -> isFieldNull(expected, s);
List<String> nullFields = filterFieldsAndGetNames(expected, nullField);
List<String> nonNullFields = filterFieldsAndGetNames(expected, nullField.negate());
nullFieldsShouldBeMissing(nullFields, jsonNode);
nonNullFieldsShouldNonBeMissing(nonNullFields, jsonNode);
}
static Stream<Arguments> giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringNullValues() {
return Stream.of(
Arguments.of(new Employee(1L, "John", "Doe")),
Arguments.of(new Employee(1L, null, "Doe")),
Arguments.of(new Employee(1L, "John", null)),
Arguments.of(new Employee(1L, null, null))
);
}
}