Fermer

juillet 31, 2019

Se moquer et tester GraphQL dans React


Tester GraphQL ne doit pas être difficile. Cet article explore les méthodes statiques et dynamiques pour faciliter le test de GraphQL.

Imaginez que vous travailliez sur une nouvelle fonctionnalité avec une autre équipe. Vous êtes responsable du côté React, mais quelqu'un d'autre est responsable des modifications de GraphQL. Est-il possible pour vous de développer votre camp avant le s'ils ont fini le leur?

ou de vouloir tester vos composants sans envoyer de véritables requêtes GraphQL au serveur? Avec Mocks, les deux sont possibles! Les simulacres vous permettent de fournir de fausses réponses à vos requêtes, vous permettant ainsi de tester entièrement vos composants sans interagir avec un serveur réel.

Dans cet article, qui suppose des connaissances préalables de React et de GraphQL, nous allons nous intéresser de deux manières différentes. simulez des réponses à une requête GraphQL. Le premier est plus facile, mais peut être un peu plus rigide avec MockedProvider . La deuxième façon nous permet de définir de faux résolveurs et de générer nos données de test de manière dynamique. Une grande partie de mon inspiration pour cet article est venue d'une conférence donnée par Chang Wang à GraphQL Day Toronto.

Le code de base final est disponible ici: https: // github.com/leighhalliday/apollo-generating-types[19659006Faireunessaidecequenoustestons

Nous allons travailler avec l'API GraphQL de Shopify pour afficher certains produits. La requête permettant d'extraire ces données se présente comme suit:

 export const PRODUCTS_QUERY = gql`
  requête ProductsData ($ preferredContentType: ImageContentType) {
    produits (premier: 10) {
      bords {
        noeud {
          identifiant
          Titre
          images (premier: 3) {
            bords {
              noeud {
                identifiant
                transformerSrc (
                  largeur maximale: 150
                  maxHauteur: 100
                  preferredContentType: $ preferredContentType
                )
              }
            }
          }
        }
      }
    }
  }
`; 

Le composant qui exécute la requête ci-dessus et affiche ses résultats se présente comme suit:

 fonction d'exportation par défaut Products () {
  revenir (
    
      {({data, loading, error}) => {
        si (erreur) {
          return 
Erreur lors du chargement des produits ...
;         }         if (chargement ||! data) {           return
Chargement des produits ...
;         }         revenir (           
            {data.products.edges.map (({node: product}) => (               

{product.title}

ID {product.id}

    {product.images.edges.map (                     ({node: image}, index: number) => (                       
  • )                   )}                 
))}}           
        )       }}     
  ) }

Si vous souhaitez en savoir plus sur l'utilisation de TypeScript et Apollo GraphQL avec des types à génération automatique, veuillez vous reporter à de cet article .

Utilisation de MockedProvider

La ​​première approche de moquage cette requête GraphQL doit utiliser quelque chose appelé un MockedProvider . En gros, il recherche une requête spécifique et, lorsqu'il voit cette requête, utilise une réponse prédéfinie. Vous vous retrouvez avec un tableau de répliques, chacune avec une requête et le résultat correspondant.

Dans ce cas, j'ai importé la requête PRODUCTS_QUERY du fichier dans lequel elle est utilisée, en veillant à ce que je répète la même chose. valeurs de variable utilisées dans le composant que nous testons (sinon, il ne correspondra pas).

 // importations requises pour l'extrait de code ci-dessous
import {ImageContentType} de "./generated/globalTypes";
importer des produits, {PRODUCTS_QUERY} de "./Products";

const se moque = [{
  request: {
    query: PRODUCTS_QUERY,
    variables: {
      preferredContentType: ImageContentType.JPG
    }
  },
  result: {
    data: {
      products: {
        edges: [{
          node: {
            id: "123",
            title: "Nike Shoes",
            images: {
              edges: [{
                node: {
                  id: "456",
                  transformedSrc: "https://www.images.com/shoe.jpg"
                }
              }]
            }
          }
        }]
      }
    }
  }
}]; 

La fermeture de tous ces objets et tableaux peut s'avérer fastidieuse, mais l'objectif est de faire correspondre à la structure de données exactement comment vous vous attendez à le récupérer depuis le serveur.

Avec Apollo, à chaque fois. vous utilisez le composant Query . Pour qu'il puisse exécuter cette requête, il doit être situé dans un fournisseur . Ce fournisseur fournit le contexte nécessaire pour résoudre les requêtes en cours d'exécution. C'est là qu'entre en jeu le MockedProvider . Nous allons envelopper ce fournisseur autour de notre composant, ce qui permettra à nos simulacres de résoudre avec de fausses données plutôt que de passer un véritable appel d'API.

 it ("rend avec MockedProvider", async () => {
  const {findByText, getByText} = render (
    
      
    
  );

  expect (getByText ("Chargement de produits ...")). toBeInTheDocument ();
  const productTag = wait findByText ("Nike Shoes");
  expect (productTag) .toBeInTheDocument ();
[)]; 

Si la bibliothèque de test-tests est nouvelle pour vous, j’ai écrit une introduction qui pourrait être utile.

Inconvénients de MockedProvider

Alors que le MockedProvider vous permet d'être opérationnel rapidement. Il peut être fastidieux de définir toutes vos données pour chaque test et chaque scénario. Si vous souhaitez simuler 15 produits, vous devez définir une grande quantité de données simulées, puis, si vous souhaitez ajouter un champ supplémentaire, vous devez modifier chacun des 15 produits simulés. Ce genre de chose se fatigue très vite.

Dans la section suivante, nous tenterons de surmonter ces inconvénients en utilisant une approche un peu plus compliquée, mais comportant de nombreuses améliorations.

Dynamic Mocks

Si le MockedProvider était un peu trop rigide à votre goût, vous serez peut-être intéressé de savoir qu'il existe un moyen de rendre les simulacres dynamiques! En termes généraux, cette approche prend un schéma GraphQL (défini manuellement ou, comme nous le verrons, téléchargé depuis l’API GraphQL actuelle via une requête d’introspection), et nous permet de définir des résolveurs simulés pour chaque type de données, avec autant ou autant que nécessaire.

Obtention du schéma

Le schéma GraphQL définit le fonctionnement de l'API GraphQL: Quelles requêtes et quelles mutations peuvent être exécutées et quels types sont définis? Dans cette approche, nous allons commencer par saisir le schéma de l'API GraphQL, ce qui peut être fait à l'aide de la commande schema: download fournie par le paquet apollo . Nous allons nous retrouver avec un fichier schema.json à la racine de notre projet, contenant l'intégralité de la sortie de l'API introspectée.

 yarn run apollo schema: download 
  --endpoint https://graphql.myshopify.com/api/graphql 
  --header "Jeton d'accès X-Shopify-Store: 078bc5caa0ddebfa89cccb4a1baa1f5c" 

Création du modèle AutoMockedProvider

nous devons définir notre propre AutoMockedProvider . Un grand nombre d'importations sont nécessaires pour cette fonctionnalité, mais nous allons explorer ce que chacun fait lorsqu'il le faut.

 import React, {ReactNode} from "react";
importer {ApolloProvider} de "react-apollo";
importer {ApolloClient} de "apollo-client";
importer {InMemoryCache} de "apollo-cache-inmemory";
importer {SchemaLink} de "apollo-link-schema";
import {makeExecutableSchema, addMockFunctionsToSchema, IMocks} à partir de "graphql-tools";
importer {printSchema, buildClientSchema} de "graphql / utilities";
import introspectionResult de "../../schema.json";[

Next, nous pouvons définir notre composant AutoMockedProvider . J'ai supprimé certaines définitions TypeScript pour permettre au code de se lire un peu plus proprement, mais si TypeScript vous intéresse, je les ai laissées dans la base de code actuelle de GitHub.

 fonction par défaut d'exportation AutoMockedProvider ({children, mockResolvers }) {
  // 1) Conversion du schéma JSON en langage de définition de schéma
  const schemaSDL = printSchema (
    buildClientSchema ({__schema: introspectionResult .__ schéma})
  )

  // 2) Rendre le schéma "exécutable"
  const schema = makeExecutableSchema ({
    typeDefs: schemaSDL,
    resolverValidationOptions: {
      requireResolversForResolveType: false
    }
  });

  // 3) Appliquer des résolveurs factices au schéma exécutable
  addMockFunctionsToSchema ({schéma, mock: mockResolvers});

  // 4) Définir ApolloClient (variable client utilisée ci-dessous)
  const client = new ApolloClient ({
    lien: new SchemaLink ({schema}),
    cache: new InMemoryCache ()
  });

  retour  {enfants} ;
} 

Avec le AutoMockedProvider défini, nous pouvons l’utiliser comme notre fournisseur Apollo mais comme nous le verrons dans la section suivante, c’est là que le plaisir et la souplesse commence.

 
  
  

Priorité aux résolveurs

La ​​fonction addMockFunctionsToSchema fournit immédiatement des résolveurs par défaut pour tous les types de base Scalar fournis avec GraphQL ( String ID Boolean etc.). Cela signifie que, par défaut, une chaîne sera résolue en Hello World et que chaque autre type a sa propre valeur par défaut.

Si une API GraphQL fournit des valeurs Scalar personnalisées ou si vous souhaitez fournir Selon vos propres valeurs, vous pouvez fournir des résolveurs factices personnalisés, ce qui permet une flexibilité totale par rapport à notre AutoMockedProvider .

 it ("rend avec AutoMockedProvider", async () => {
  const mockResolvers = {
    Produit: () => ({titre: "Chaussures Nike"}),
    URL: () => "https://www.shopify.com"
  };

  const {findByText, getByText} = render (
    
      
    
  );

  expect (getByText ("Chargement de produits ...")). toBeInTheDocument ();
  const productTag = wait findByText ("Nike Shoes");
  expect (productTag) .toBeInTheDocument ();

Dans ce cas, nous avons annulé ce que le champ title du type Product sera et aura fourni un résolveur pour le type scalaire personnalisé . URL . Une erreur se produira si aucun résolveur personnalisé n'est fourni pour les types Scalar personnalisés.

Personnalisation des éléments de tableau avec MockList

Par défaut, chaque fois qu'il y a un tableau d'éléments, Apollo en retournera 2. Mais que se passe-t-il si vous voulez 0, 10 ou même un nombre variable d'éléments? C'est là que l'objet MockList entre en jeu. Cela nous permettra de définir exactement le nombre d'éléments que nous voulons. Dans ce cas, nous aurons entre 0 et 3 éléments de bord d'image dans notre réponse.

 const mockResolvers = {
  Produit: () => ({
    titre: "Nike Shoes",
    images: () => ({
      bords: () => new MockList ([0, 3])
    })
  })
}; 

Accès aux arguments

Souvent, nos requêtes (et leurs champs) utilisent des arguments pour fournir des détails supplémentaires au serveur. Dans cette requête, l’API Shopify GraphQL nous permet de définir le type d’image (JPG, PNG, etc.) que nous souhaiterions obtenir en réponse. Voici comment accéder à ces arguments, ce qui vous permet de personnaliser votre résolveur simulé en fonction des arguments qui lui sont transmis:

 const mockResolvers = {
  Image: () => ({
    transformerSrc: (root, {preferredContentType}) => `https://images.com/cat.$ {preferredContentType.toLowerCase ()}`
  })

Nous pouvons maintenant donner à l'URL renvoyée par le résolveur du champ transformerSrc l'extension correspondant à l'argument transmis au champ ( .jpg dans ce cas).

Fausses valeurs cohérentes

Plutôt que de définir chaque champ, vous pouvez utiliser une bibliothèque telle que faker pour fournir des données fausses plus réalistes. Parfois, vos données sont un peu trop aléatoires, cependant. Prenons l'exemple ci-dessous où nous utilisons la fonction uuid de faker pour générer chaque ID, dans le but de produire un test d'instantané. Chaque fois que le code sera exécuté, nous aurons des UUID uniques, ce qui rendra difficile d'avoir un instantané cohérent.

Pour ce scénario, faker fournit un moyen de définir une valeur initiale, en veillant à chaque fois que ce code est exécuté, il fournira une sortie aléatoire mais cohérente faker.seed (123) .

 it ("correspond à l’instantané utilisant des graines", async () => {
  faker.seed (123);
  const {findByTestId, asFragment} = render (
     "https://www.shopify.com",
        ID: () => faker.random.uuid ()
      }}
    >
      
    
  );

  wait findByTestId ("result");
  expect (asFragment ()). toMatchSnapshot ();
}); 

Conclusion

Dans cet article, nous avons vu deux manières différentes de simuler des requêtes GraphQL pour nos tests. La première approche utilisait le MockedProvider nous permettant de définir explicitement quelles données seraient renvoyées pour chaque requête. Cela fonctionne bien mais peut rapidement devenir fastidieux et difficile à maintenir.

La deuxième approche consistait à créer une fonction AutoMockedProvider à l'aide de la fonction addMockFunctionsToSchema d'Apollo, nous permettant de définir et d'annuler les résolvers. pour chaque type de données et chaque champ, il suffit de les définir explicitement lorsque cela est nécessaire. D'après mon expérience, c'est la voie à suivre, offrant une flexibilité extrême sans trop de frais généraux supplémentaires.







Source link