Tests
Disponible Ă partir de la version 2.0.0-rc.1 !
Général :
La génération de views permet également de générer des tests unitaires sur les fichiers JSX.
En effet, des tests de composant sont générés à partir des données fournies à chaque views.
Si vous modifiez le fichier .yml de votre vue, il est nécessaire de supprimer le ficher de test associé avant de re-lancer la commande yarn build:views
Les tests générés utilisent les configurations de base présentes dans le starter.
Test de views
Chaque fichier view.jsx a un fichier associé view.inte.test.js qui contiendra tout ou une partie des tests. Il est tout à fait possible de le découper en plusieurs fichiers de tests pour une views en les répartissant par domaine fonctionnel, par exemple.
Génération des tests
Les fichiers de tests générés comportent plusieurs éléments :
- beforeAll qui permet de configurer l'environnement GQL (par la suite passé au projectProviders puis au RelayEnvironmentProvider)
- Un setupServer qui permet de configurer et mocker certains appels REST et GQL
- Les donnĂ©es des queries et mutations doivent ĂȘtre renseignĂ©es (les donnĂ©es ne sont pas calculables Ă partir des viewConfig), par exemple :
graphql.query('GetAdhesionQuery', (req, res, ctx) => {
return res(
ctx.data({
getAdhesion_adhesion: {
a_valid_key: 'a_valid_value'
}
})
);
})- Ce setupServer prend en paramĂštre un Array (tableau) pour permettre Ă chaque test de dĂ©finir ses propres mocks (ils sont ajoutĂ©s en plus de ceux dĂ©finis par dĂ©faut mais les surchargent s'ils mockent les mĂȘmes appels).
- Plusieurs tests sont par défaut skip (pour ne pas casser l'existant):
Il est possible de désactiver la génération des fichiers de tests d'intégration à partir de la version 2.1.0
Pour désactiver la génération des fichiers de tests d'intégration ajouter la clé testFile au fichier de configuration de la vue. Exemple :
home:
viewId: HOME
pathname: /
directory: home
actions:
- NEXT
- _DEFAULT_
testFile: false # test files generation disabled
graphql:
queries:
- type: Project
fields:
id: String
externalId: String
name: String
resolver: getProjectUn test d'affichage
Voici ce qui est fait dans le test :
- Exécution du setupServer pour mocker les appels externes.
- Rendu du composant.
- Attente (findBy) du retour des appels API et query GraphQL interceptés via MSW.
- Snapshot du visuel stabilisé.
it('simple test - snapshot', async () => {
setupServer();
const { container } = render(<Home />, {
wrapper: ({ children }) => <TestsProviders environment={environment}>{children}</TestsProviders>
});
await screen.findByRole('heading', { value: viewIds.HOME });
await screen.findByText('Is loaded GetProject: yes');
expect(container).toMatchSnapshot();
});Un ou plusieurs tests d'interactions sur les actions :
Voici ce qui est fait dans le test :
- Exécution du setupServer pour mocker les appels externes,
- Rendu du composant,
- Attente du retour des appels API et query GraphQL interceptés via MSW,
- Interaction avec le DOM via la librairie userEvent (type, click, dblClick, etc),
- Attente du retour de l'action effectuĂ©e, souvent une mutationQuery qui doit donc aussi ĂȘtre mockĂ©e, puis re-mock le getQuery associĂ© pour avoir de nouvelles donnĂ©es.
- Vérification que la mutationQuery a été appelée avec les bonnes données.
it('userEvent - action - NEXT', async () => {
const user = userEvent.setup();
// check if method has been called
let isMutationValid = new Promise((resolve, reject) => {
setTimeout(() => reject('did not handle the request'), 1500);
});
setupServer([
graphql.mutation('ProcessActionMutation', (req, res, ctx) => {
const { input } = req.variables;
// method has been called with the right data
isMutationValid = Promise.resolve(equals(input, { action: 'HOME_NEXT', viewId: 'HOME' }));
return res(
ctx.data({
processAction: {
ok: true,
error: null,
process: {
id: 'id',
context: {
projectId: 'projectId',
viewId: 'HOME',
actions: ['NEXT', '_DEFAULT_']
}
}
}
})
);
})
]);
render(<Home />, {
wrapper: ({ children }) => <TestsProviders environment={environment}> {children} </TestsProviders>
});
await screen.findByRole('heading', { value: viewIds.HOME });
await screen.findByText('Is loaded GetProject: yes');
const button = await screen.findByRole('button', { name: 'NEXT' });
await user.click(button);
await waitFor(() => expect(isMutationValid).toBeTruthy());
});Exemple de tests supplémentaires :
Lancement d'une mutationQuery sur une action utilisateur dans la page (bouton):
import GQLEnvironment from '@@appGraphQL/Environment';
import Start from './Start';
import { beforeAll, describe, expect, it } from '@jest/globals';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { graphql } from 'msw';
import { equals } from 'ramda';
import { TestsProviders } from 'tests/__custom__/TestsProviders';
import { server } from 'tests/__utils__/init-app-after-env.mjs';
import { viewIds } from '@@app/withProcess';
describe('Test Start', () => {
let environment;
beforeAll(async () => {
environment = await GQLEnvironment();
});
const setupServer = (otherQuery = []) => {
server.use(
...otherQuery,
graphql.query('QProcessQuery', (req, res, ctx) => {
return res(
ctx.data({
process: {
id: 'id',
context: {
projectId: 'projectId',
viewId: 'START',
actions: ['PREVIOUS']
},
userSession: null,
tasks: null
}
})
);
}),
graphql.query('GetStartProjectQuery', (req, res, ctx) => {
return res(
ctx.data({
getStartProject_start: {
id: 'String',
externalId: 'String',
name: 'String'
}
})
);
}),
graphql.query('GetCustomerQuery', (req, res, ctx) => {
return res(
ctx.data({
getCustomer_start: {
email: 'String',
firstName: 'String',
lastName: 'String'
}
})
);
})
);
};
it('simple test - create a customer', async () => {
const user = userEvent.setup();
// check if method has been called
let isMutationValid = new Promise((resolve, reject) => {
setTimeout(() => reject('did not handle the request'), 1500);
});
setupServer([
graphql.mutation('CreateCustomerMutation', (req, res, ctx) => {
const { input } = req.variables;
// method has been called with the right data
isMutationValid = Promise.resolve(
equals(input, { email: '[email protected]', firstName: 'starter', lastName: 'starter' })
);
return res(ctx.data({ createCustomer_M_start: { ok: true, error: null } }));
}),
graphql.query('GetCustomerQuery', (req, res, ctx) => {
return res(
ctx.data({
getCustomer_start: {
email: '[email protected]',
firstName: 'starter',
lastName: 'starter'
}
})
);
})
]);
render(<Start />, {
wrapper: ({ children }) => <TestsProviders environment={environment}> {children} </TestsProviders>
});
await screen.findByRole('heading', { value: viewIds.START });
await screen.findByText('Is loaded GetStartProject: yes');
await screen.findByText('Is loaded GetCustomer: yes');
const button = await screen.findByRole('button', { name: 'Create Customer' });
await user.click(button);
await waitFor(() => expect(isMutationValid).toBeTruthy());
await screen.findByText(/\{ "email": "starter@mail\.com", "firstname": "starter", "lastname": "starter" \}/i);
});
});