Test d'unité de sérialisation Json dans Spring Boot



Présentation


L'une des tâches principales de chaque service Web est de renvoyer le modèle côté client et, dans ce cas, Spring Boot fournit un niveau d'abstraction pratique, permettant au développeur de rester au niveau de l'utilisation des modèles et de laisser le processus de sérialisation du modèle en dehors du code source du programme. Mais que se passe-t-il si la sérialisation elle-même fait partie de la logique métier de l'application et nécessite donc une couverture de cas de test?

Dans cet article, nous considérerons l'un des scénarios où nous devrons peut-être prendre en compte les caractéristiques de la logique métier de l'application lors de la sérialisation (le scénario d'arrondir les montants d'argent), sur l'exemple duquel nous rencontrerons le mécanisme de sérialisation dans Spring Boot, et décrirons également une méthode de test possible.

Énoncé du problème


Laissez notre service Web responsable de fournir des informations sur les dépenses des clients, et nous devons fournir des données avec une précision configurable. Une solution logique serait de faire toutes les transformations du modèle à la périphérie du service, tout en conservant la visibilité de l'application de la logique d'arrondi.

Considérez une solution possible


Imaginez le contrôleur de notre application, qui renverra le modèle souhaité.

@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; } } 

Voyons maintenant notre modèle.

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

Vous avez peut-être déjà dû gérer d'autres annotations pour la personnalisation. Une caractéristique de cette annotation est la possibilité de déterminer votre service responsable de la sérialisation du champ de modèle annoté.

Avant de voir ce qu'est un sérialiseur, nous allons compliquer la tâche: laisser les paramètres d'arrondi être configurables via un service interne qui résume toute manifestation de résolution dynamique des paramètres.

Cette complication est notre point clé que nous voulons considérer. Comme nous pouvons le voir dans l'implémentation du modèle, l'API de notre framework prend une classe de sérialisation dans l'argument, ce qui signifie que le cycle de vie du sérialiseur passe sous le contrôle du framework du sérialiseur. Cela soulève la question, que faire si nous voulons injecter la dépendance du contexte de notre application dans le sérialiseur? Pour ce faire, considérez l'implémentation du sérialiseur ci-dessus.

 //     , //  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())); } } 

Notre service est prêt, mais en tant que développeurs responsables, nous voulons nous assurer que la cuisine que nous avons assemblée fonctionne.

Passons aux tests.


Voyons ce que l'API Framework de test nous offre.

 //     ,   //    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; } } } 

Arrêtons-nous plus en détail sur ce moment. Jackson utilise la classe ObjectMapper pour sérialiser et désérialiser les modèles. C'est exactement l'objet contextuel qui est responsable de la transformation des modèles. Par conséquent, pour vous assurer de la présentation du modèle, vous devez vérifier comment ObjectMapper le traite à partir du contexte.

Si vous souhaitez créer votre propre ObjectMapper personnalisé, vous pouvez rencontrer l'exemple typique suivant: ObjectMapper mapper = new ObjectMapper . Cependant, regardez comment Spring Boot crée une instance de cette classe par défaut. Pour ce faire, nous nous tournons vers le code d' autoconfiguration JacksonAutoConfiguration d' origine, qui est responsable de la création de l'objet:

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

Et si nous allons encore plus loin et regardons build () , nous constatons que pour la sérialisation, à laquelle nous pourrions nous habituer lorsque nous travaillons avec le mappeur par défaut (comme l'injection de services dans un sérialiseur personnalisé), il ne suffit pas de créer un mappeur Bean, vous devez vous tourner vers le générateur fourni . Soit dit en passant, dans la documentation Spring Boot elle-même, cela est explicitement indiqué.

Avec une digression, je voudrais ajouter une référence à JacksonTester . En tant que représentant du shell pour les tests de sérialisation BDD dans le contexte de Mockito.

Pour résumer


  • Spring Boot offre la possibilité de personnaliser la sérialisation du modèle via des annotations JsonSerializer
  • Pour tester la sérialisation, utilisez le mappeur dans la même configuration que dans l'application
  • Lorsque vous remplacez un bean à partir de la configuration automatique de Spring Boot, faites attention à la façon dont ce bean crée Spring Boot lui-même, afin de ne pas manquer les opportunités que le bean par défaut avait
  • Vous pouvez utiliser l'annotation JsonTest pour spécifier le contexte limité requis pour tester la sérialisation.

Conclusion


Merci de votre attention! Cet exemple sera pertinent pour la version actuelle de Spring Boot 2.1.x, ainsi que pour les versions antérieures jusqu'à 1.4.x. De plus, la technique convient aux situations de désérialisation du modèle. Regardez sous le capot de vos infrastructures de base pour mieux comprendre le mécanisme de fonctionnement de l'application et adoptez une approche responsable des tests.

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


All Articles