Testen der Json-Serialisierungseinheit in Spring Boot



Einführung


Eine der Hauptaufgaben jedes Webdienstes besteht darin, das Modell an die Clientseite zurückzugeben. In diesem Fall bietet Spring Boot eine bequeme Abstraktionsebene, sodass der Entwickler auf der Ebene der Arbeit mit Modellen bleiben und den Modellserialisierungsprozess außerhalb des Programmquellcodes belassen kann. Was aber, wenn die Serialisierung selbst Teil der Geschäftslogik der Anwendung wird und daher eine Testfallabdeckung erfordert?

In diesem Artikel werden wir eines der Szenarien betrachten, in denen wir möglicherweise die Merkmale der Geschäftslogik der Anwendung während der Serialisierung berücksichtigen müssen (das Szenario der Rundung von Geldbeträgen). An diesem Beispiel werden wir den Serialisierungsmechanismus in Spring Boot kennenlernen und auch eine mögliche Testmethode beschreiben.

Erklärung des Problems


Lassen Sie unseren Webservice für die Bereitstellung von Informationen über Kundenkosten verantwortlich sein, und wir müssen Daten mit konfigurierbarer Genauigkeit bereitstellen. Eine logische Lösung wäre, alle Modelltransformationen an der Peripherie des Dienstes durchzuführen und dabei die Sichtbarkeit der Anwendung der Rundungslogik beizubehalten.

Überlegen Sie sich eine mögliche Lösung


Stellen Sie sich den Controller unserer Anwendung vor, der das gewünschte Modell zurückgibt.

@RestController public class AccountController { //        , //       . @Autowired private AccountService accountService; @RequestMapping(value = "/account/{clientId}", method = RequestMethod.GET, produces = "application/json") public Account getAccount(@PathVariable long clientId) throws Exception { Account result = accountService.getAccount(clientId); //  ,    - //      ,    json, //    . return result; } } 

Schauen wir uns nun unser Modell an.

 public class Account { private Long clientId; //    Spring Boot   FasterXML/jackson, //    API  ,   . // ,       //     MoneySerializer @JsonSerialize(using = MoneySerializer.class) private BigDecimal value; //    getter'  setter'    } 

Möglicherweise mussten Sie sich bereits mit anderen Anmerkungen zur Anpassung befassen. Ein Merkmal dieser Annotation ist die Möglichkeit, Ihren Service zu bestimmen, der für die Serialisierung des Felds mit annotierten Modellen verantwortlich ist.

Bevor wir uns ansehen, was ein Serializer ist, werden wir die Aufgabe komplizieren: Lassen Sie Rundungsparameter über einen internen Dienst konfigurierbar sein und abstrahieren Sie alle Manifestationen der dynamischen Parameterauflösung.

Diese Komplikation ist unser zentraler Punkt, den wir berücksichtigen möchten. Wie aus der Implementierung des Modells ersichtlich ist, verwendet die API unseres Frameworks eine Serialisierungsklasse im Argument, was bedeutet, dass der Lebenszyklus des Serializers unter der Kontrolle des Serializer-Frameworks steht. Dies wirft die Frage auf, was zu tun ist, wenn die Abhängigkeit vom Kontext unserer Anwendung in den Serializer eingefügt werden soll. Berücksichtigen Sie dazu die obige Implementierung des Serializers.

 //     , //  Jackson   Spring, // API    //    Spring DI @JsonComponent public class MoneySerializer extends JsonSerializer<BigDecimal> { //  ,   , //    Spring Boot    Bean'. private RoundingHolder roundingHolder; @Autowired public MoneySerializer(RoundingHolder roundingHolder) { this.roundingHolder = roundingHolder; } //       , // ,   ,      - //      . @Override public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeNumber(value.setScale(roundingHolder.getPrecision(), roundingHolder.getRoundingMode())); } } 

Unser Service ist bereit, aber als verantwortungsbewusste Entwickler möchten wir sicherstellen, dass die von uns zusammengestellte Küche funktioniert.

Fahren wir mit dem Testen fort.


Mal sehen, was uns die Test-Framework-API bietet.

 //     ,   //    Spring,       . //     JsonTest,      //      , //    JSON-. @JsonTest @ContextConfiguration(classes = {AccountSerializationTest.Config.class}) @RunWith(SpringRunner.class) public class AccountSerializationTest { //  ,      //     ObjectMapper,    . //       . //    , //      . @Autowired private ObjectMapper objectMapper; @Test public void testAccountMoneyRounding() throws Exception { Account account = new Account(); account.setClientId(1L); account.setValue(BigDecimal.valueOf(1.123456789)); String expectedResult = "{\"clientId\":1,\"value\":\1.123\}"; // ,          JSON, //     -. assertEquals(expectedResult, objectMapper.writeValueAsString(account)); } //   MoneySerializer   API  //    ,       //    Jackson.   ,   , //   Spring , ,  //      . @TestConfiguration public static class Config { @Bean public static RoundingHolder roundingHolder() { RoundingHolder roundingHolder = Mockito.mock(RoundingHolder.class); //   ,         Mockito.when(roundingHolder.getMathContext()).thenReturn(new MathContext(3, RoundingMode.HALF_EVEN)); return roundingHolder; } } } 

Lassen Sie uns diesen Moment genauer betrachten. Jackson verwendet die ObjectMapper- Klasse, um Modelle zu serialisieren und zu deserialisieren. Dies ist genau das Kontextobjekt, das für die Transformation von Modellen verantwortlich ist. Um sicherzustellen, wie das Modell dargestellt wird, müssen Sie daher überprüfen, wie ObjectMapper es aus dem Kontext verarbeitet.

Wenn Sie Ihren eigenen benutzerdefinierten ObjectMapper erstellen möchten, können Sie auf das folgende typische Beispiel stoßen: ObjectMapper mapper = new ObjectMapper . Sehen Sie sich jedoch an, wie Spring Boot standardmäßig eine Instanz dieser Klasse erstellt. Dazu wenden wir uns dem ursprünglichen Autokonfigurationscode von JacksonAutoConfiguration zu , der für die Erstellung des Objekts verantwortlich ist:

 @Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { return builder.createXmlMapper(false).build(); } 

Wenn wir uns noch weiter mit build () befassen, stellen wir fest, dass es für die Serialisierung, an die wir uns bei der Arbeit mit dem Standard-Mapper gewöhnen könnten (z. B. das Einfügen von Diensten in einen benutzerdefinierten Serializer), nicht ausreicht, nur einen Bean-Mapper zu erstellen. Wenden Sie sich an den bereitgestellten Builder . Dies wird übrigens in der Spring Boot- Dokumentation selbst ausdrücklich angegeben.

Mit einem Exkurs möchte ich einen Verweis auf JacksonTester hinzufügen. Als Vertreter der Shell für BDD-Serialisierungstests im Rahmen von Mockito.

Zusammenfassend


  • Spring Boot bietet die Möglichkeit, die Modellserialisierung über JsonSerializer-Anmerkungen anzupassen
  • Verwenden Sie zum Testen der Serialisierung den Mapper in derselben Konfiguration wie in der Anwendung
  • Achten Sie beim Überschreiben einer Bean aus der automatischen Spring Boot-Konfiguration darauf, wie diese Bean Spring Boot selbst erstellt, um die Möglichkeiten der Standard-Bean nicht zu verpassen
  • Mit der Annotation JsonTest können Sie den begrenzten Kontext angeben, der zum Testen der Serialisierung erforderlich ist.

Fazit


Vielen Dank für Ihre Aufmerksamkeit! Dieses Beispiel ist für die aktuelle Version von Spring Boot 2.1.x sowie für frühere Versionen bis 1.4.x relevant. Die Technik eignet sich auch für Situationen mit Deserialisierung des Modells. Schauen Sie unter die Haube Ihrer Kern-Frameworks, um den Funktionsmechanismus der Anwendung besser zu verstehen, und gehen Sie beim Testen verantwortungsbewusst vor.

Source: https://habr.com/ru/post/de452188/


All Articles