Вопрос или проблема
У меня есть универсальный класс, который используется в качестве ответа всеми ресурсами в моем API
public class TestResponse<T> {
private boolean result;
private int status;
private T data;
// Геттеры и сеттеры
}
У меня есть еще два класса, которые используются для инициализации поля TestResponse.Data
из двух разных ресурсов
public class TestResponseData01 {
private int id;
// Геттеры и сеттеры
}
public class TestResponseData02 {
private String message;
// Геттеры и сеттеры
}
Один ресурс возвращает ответ типа TestResponse<TestResponseData01>
, а другой TestResponse<TestResponseData02>
В тестовом классе я создал метод, который выполняет общие тесты ответов всех ресурсов, но я хотел бы вернуть экземпляр TestResponse, чтобы затем выполнить специфические тесты в зависимости от ресурса, который я тестирую.
private <T> TestResponse<T> commonTest(Response response, Class<T> dataType) {
TestResponse<T> testResponse = response.readEntity(new GenericType<TestResponse<T>>(){});
// ...
}
Проблема в том, что выражение TestResponse<T> testResponse = response.readEntity(new GenericType<TestResponse<T>>(){})
десериализует JSON ответа, но для поля TestResponse.Data
оно создает объект типа java.util.LinkedHashMap
Как я могу выполнить метод readEntity, чтобы поле TestResponse.Data
было правильно инициализировано объектом TestResponseData01
или TestResponseData02
?
Полный код
pom.xml зависимости
<properties>
<project.build.sourceEncoding>UTF-8</project.build.source.encoding>
<lombok.version>1.18.30</lombok.version>
<jersey.version>2.25.1</jersey.version>
<junit.version>5.11.2</junit.version>
</properties>
<dependencies>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Jersey -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>${jersey.version}</version>
</dependency>
<!-- JUnit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Тестирование Jersey -->
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Класс JerseyTest
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@TestInstance(Lifecycle.PER_CLASS)
public class Test_GenericType extends JerseyTest {
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public static class TestResponse<T> {
private boolean result;
private int status;
private T data;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public static class TestResponseData01 {
private int id;
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public static class TestResponseData02 {
private String message;
}
@Path("testData01")
@Produces(MediaType.APPLICATION_JSON)
public static class TestResponseData01Resource {
@GET
public Response test() {
TestResponseData01 testResponseData01 = new TestResponseData01(1);
TestResponse<TestResponseData01> testResponse = new TestResponse<TestResponseData01>(true, Response.Status.OK.getStatusCode(), testResponseData01);
return Response.status(Response.Status.OK).entity(testResponse).build();
}
}
@Path("testData02")
@Produces(MediaType.APPLICATION_JSON)
public static class TestResponseData02Resource {
@GET
public Response test() {
TestResponseData02 testResponseData02 = new TestResponseData02("a");
TestResponse<TestResponseData02> testResponse = new TestResponse<TestResponseData02>(true, Response.Status.OK.getStatusCode(), testResponseData02);
return Response.status(Response.Status.OK).entity(testResponse).build();
}
}
@Override
protected Application configure() {
return new ResourceConfig(TestResponseData01Resource.class, TestResponseData02Resource.class);
}
@BeforeAll
public void before() throws Exception {
super.setUp();
}
@AfterAll
public void after() throws Exception {
super.tearDown();
}
private <T> TestResponse<T> commonTest(Response response, Class<T> dataType) {
TestResponse<T> testResponse = response.readEntity(new GenericType<TestResponse<T>>(){});
assertTrue(testResponse.isResult());
assertEquals(200, testResponse.getStatus());
return testResponse;
}
@Test
void Test_ResponseData01Resource() {
Response response = target("testData01").request().get();
TestResponse<TestResponseData01> testResponse = commonTest(response, TestResponseData01.class);
TestResponseData01 testData = testResponse.getData();
assertEquals(1, testData.getId());
}
@Test
void Test_ResponseData02Resource() {
Response response = target("testData02").request().get();
TestResponse<TestResponseData02> testResponse = commonTest(response, TestResponseData02.class);
TestResponseData02 testData = testResponse.getData();
assertEquals("a", testData.getMessage());
}
}
Ответ или решение
Решение проблемы с десериализацией в JerseyTest: генерические типы
Введение
Вас беспокоит проблема корректной десериализации поля data
в вашем классе TestResponse
, который является обобщенным и применяется в различных ресурсах вашего API. При чтении ответа вы столкнулись с тем, что тип поля data
возвращается как объект типа LinkedHashMap
, а не как ожидаемый тип, такой как TestResponseData01
или TestResponseData02
. Данная статья предлагает решение вашего вопроса, рассматривая, как правильно настроить десериализацию с использованием JerseyTest.
Проблема
Ваша реализация метода commonTest
, использующего response.readEntity(new GenericType<TestResponse<T>>() {})
, не учитывает тип data
. Из-за этого при десериализации JSON ответа структура, соответствующая типам TestResponseData01
и TestResponseData02
, теряется.
Решение
Для того чтобы корректно десериализовать ответ JSON с учетом специфики обобщенных типов, необходимо подробнее рассмотреть, как используется GenericType
. Вам нужно передать дополнительную информацию о типе data
в GenericType
, чтобы указывать, какого типа объект ожидается.
Шаги для решения
-
Создайте специализированный
GenericType
для каждого теста. Вам потребуется создать анонимный класс, который будет указывать конкретный тип дляT
. -
Обновите метод
commonTest
следующим образом:
private <T> TestResponse<T> commonTest(Response response, Class<T> dataType) {
GenericType<TestResponse<T>> genericType = new GenericType<TestResponse<T>>() {
@Override
public String getTypeName() {
return "TestResponse<" + dataType.getSimpleName() + ">";
}
};
TestResponse<T> testResponse = response.readEntity(genericType);
// Проверки состояния ответа
assertTrue(testResponse.isResult());
assertEquals(200, testResponse.getStatus());
return testResponse;
}
- Измените вызовы метода в тестах:
Вам нужно будет выполнить вызов метода commonTest
с указанием типа data
:
@Test
void Test_ResponseData01Resource() {
Response response = target("testData01").request().get();
TestResponse<TestResponseData01> testResponse = commonTest(response, TestResponseData01.class);
TestResponseData01 testData = testResponse.getData();
assertEquals(1, testData.getId());
}
@Test
void Test_ResponseData02Resource() {
Response response = target("testData02").request().get();
TestResponse<TestResponseData02> testResponse = commonTest(response, TestResponseData02.class);
TestResponseData02 testData = testResponse.getData();
assertEquals("a", testData.getMessage());
}
Заключение
Таким образом, для корректной десериализации поля data
в вашем классе TestResponse
вы должны предоставить информацию о конкретном типе в момент вызова метода readEntity
. Использование специализированного GenericType
позволяет избежать ситуации, когда данные десериализуются в LinkedHashMap
, и обеспечивает правильное создание объектов вашего обобщенного типа. Это решение улучшит стабильность и предсказуемость ваших тестов, что жизненно важно для эффективного API.