IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tester son code Apex

Comment tester son développement Apex avant une mise en production dans Salesforce ?

6 commentaires Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Lors d'une mise en production d'une fonctionnalité Salesforce ayant nécessité un développement Apex (trigger, contrôleur…), il est obligatoire que celle-ci ait une couverture de code d'au moins 75 % et que l'exécution de cette couverture ne provoque aucune erreur (non traitée du moins) pour que la mise en production soit validée par Salesforce.

Avant d'aller plus loin, je vous propose quelques rappels sur les termes qui vont être abordés tout au long de ce tutoriel :

- SalesforceSalesforce est un CRM (Customer Relationship Manager) orienté SaaSSaaS (Software as a Service) et PaaSPaaS (Platform as a Service) permettant la gestion des relations clients d'une entreprise ;

- Apex, est un langage de programmation côté serveur permettant de modifier la logique métier et le traitement des données ;

- un trigger est un morceau de code Apex qui s'exécute avant et/ou après :

  • une création d'enregistrements ;
  • une mise à jour d'enregistrements ;
  • une suppression d'enregistrements ;

- WSDL (Web Services Description Language) est, comme son nom l'indique, un fichier au format XML décrivant le web service, c'est-à-dire les méthodes que vous pouvez appeler avec les paramètres attendus, la valeur de retour ainsi que les types de ces valeurs et l'URL d'appel de ce web service pour faire simple.

Maintenant que vous avez connaissance des définitions, je vais vous montrer comment créer une classe de tests qui va nous servir à couvrir notre code, mais également comment vous assurer que le résultat retourné correspond bien à celui attendu. Pour cela, nous allons, dans un premier temps, écrire un trigger qui se déclenchera lors de la création et de la mise à jour d'enregistrements de type «  Case  », pour ensuite créer sa classe de tests unitaires. Je finirai par vous expliquer comment tester l'appel d'un service web en simulant les données retournées par celui-ci.

Le scénario est assez simple, lorsqu'un enregistrement de type «  Case  » sera créé ou mis à jour, nous le rattacherons au contact correspondant à l'adresse mail de cet enregistrement, si celui-ci n'est pas vide. Dans le cas où l'adresse mail est vide ou que le contact n'existe pas, nous allons le créer puis le rattacher au «  Case  » approprié.

II. Prérequis

Avant toute chose, vous devez disposer d'une instance afin de pouvoir suivre ce tutoriel. Si ce n'est pas le cas, je vous invite à vous rendre à cette adresseadresse. Salesforce met à disposition plusieurs types d'instances qui proposent différentes fonctionnalités et donc à différents prix (prix par licence utilisateur par mois). La seule instance que vous pourrez généreusement obtenir de la part de Salesforce est celle de développement (celle que je vous propose via le lien ci-dessus) qui, comme son nom l'indique, sert à tester et développer des services. Je vous laisse juger par vous-même la grille tarifairegrille tarifiare sur les diverses instances que propose le CRM.

Vous aurez également besoin d'un environnement de développement pour écrire votre code, vous pouvez utiliser celui fourni par Salesforce ou alors vous procurer celui compatible avec Eclipse et qui s'appelle Force.com IDE (je l'utiliserai tout au long de ce tutoriel). Pour ce dernier, rendez-vous sur l'Eclipse MarketPlace et installez-le.

III. Écriture du trigger

Comme je vous l'ai dit plus haut, un trigger est un bout de code Apex qui s'exécute avant et/ou après :

  • la création d'un enregistrement ;
  • la mise à jour d'un enregistrement ;
  • la suppression d'un enregistrement.

À savoir que lorsque l'on écrit un trigger ou du code Apex de manière générale, Salesforce impose des limites que nous devons impérativement respecter pour que notre développement puisse être validé et déployé en production. Par exemple, nous ne devons pas excéder plus de 100 requêtes SOQL (Salesforce Object Query Language), il faut donc réfléchir en termes de List. Je veux dire par là que si, par exemple, le trigger se déclenche et qu'il a une cinquantaine d'enregistrements à traiter et que nous avons besoin de faire des requêtes vers la base de données, nous n'allons pas faire une requête par enregistrement en utilisant la condition «  WHERE Id = ''  », mais plutôt faire une seule requête en spécifiant à la condition un tableau contenant tous les identifiants que nous souhaitons, ce qui donnerait la condition «  WHERE Id IN:monTableau  ». Ainsi, nous ne faisons qu'une seule requête au lieu de n.

Salesforce a récemment imposé que toute transaction n'excède pas les 10 secondes de traitement CPU (du côté des serveurs Salesforce). Cela exclut les temps d'accès à la base de données et ne prend en compte uniquement que le traitement de données (triggers, méthodes de classes, etc.).

Vous pouvez visualiser le temps d'exécution d'un processus à l'aide de la méthode Limits.getCpuTime.

Maintenant que vous êtes initié aux limitations imposées par Salesforce, passons à la logique de notre trigger.

Voilà la logique que je propose pour développer notre script Apex :

1. Parcours de la liste des cases pour récupérer les mails de ceux qui ne sont pas reliés à un contact (champ «  contactId  » null) et qui ont le champ «  SuppliedEmail  » (celui qui nous intéresse) renseigné ;

2. Nous interrogeons Salesforce pour récupérer les contacts qui ont le champ «  Email  » correspondant aux adresses mails récupérées dans les cases ;

3. Nous reparcourons la liste des cases qui ne sont pas liés à un contact, mais qui ont le champ «  SuppliedEmail  » renseigné ;

4. Si le contact correspondant à l'adresse mail du case existe parmi ceux qui ont été récupérés dans Salesforce, nous relions le case au contact. Dans le cas contraire, nous créons le contact en lui ajoutant l'adresse mail dans le champ «  Email  » et également dans le champ «  Name  » puisque celui-ci est obligatoire pour créer le contact ;

5. Nous créons les contacts dans Salesforce (pour qu'ils obtiennent un identifiant) ;

6. Nous parcourons les cases qui, précédemment, n'avaient pas pu être liés à un contact ;

7. Nous lions chaque case au contact correspondant à l'adresse mail.

Maintenant que je vous ai expliqué la logique du trigger, voici son code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
trigger JoinContactToCase on Case (before insert, before update) {

    public map<String, Contact> contactsMap = new map<String, Contact>();
    public map<String, Contact> createdContacts;
    public List<String> mailsList = new List<String>();
    public List<Contact> contactToCreate = new List<Contact>();
    public List<Integer> remainderCases = new List<Integer>();
    Integer caseNumber = 0;
    
    // Pour chaque case
    for(Case myCase : Trigger.new){
        
        // Si le contactId est null mais que le champ suppleidMail ne l'est pas
        if(myCase.ContactId == null && myCase.SuppliedEmail != null){
            
            // Récupération de l'adresse mail
            mailsList.add(myCase.SuppliedEmail);
        }       
    }
    
    // Récupération des contacts associés aux adresses mails
    contactsMap = getContacts(mailsList);
    
    // Parcours des cases
    for(Case myCase : Trigger.new){
        
        if(myCase.ContactId == null && myCase.SuppliedEmail != null){
            
            // Si le contact avec l'adresse mail du case existe dans la map
            if(contactsMap.containsKey(myCase.SuppliedEmail)){
                myCase.ContactId = contactsMap.get(myCase.SuppliedEmail).Id;
            }
            // On ajoute le contact à la liste pour le créer par la suite
            else{
                contactToCreate.add(new Contact(
                    Email = myCase.SuppliedEmail,
                    LastName = myCase.SuppliedEmail
                ));
                
                // On indique quel case il reste à relier à un contact
                remainderCases.add(caseNumber);
            }
        }
        
        caseNumber++;
    }
    
    // Création des contacts
    insert contactToCreate;
    
    // Récupération des contacts créés sous forme de map<email, contact>
    createdContacts = getContactsByMap(contactToCreate);
    
    // Pour chaque case qu'il reste à relier à un contact
    for(Integer theCaseNumber : remainderCases){
        
        // Si le contact de l'adresse mail du case est un contact venant d'être créé
        if(createdContacts.containsKey(Trigger.new[theCaseNumber].SuppliedEmail)){
            
            // Liaison du case avec le contact
            Trigger.new[theCaseNumber].ContactId = createdContacts.get(Trigger.new[theCaseNumber].SuppliedEmail).Id;
        }
    }
    
    /** Retourne les contacts associés aux adresses mails données en paramètre sous forme de map<mail, contact> **/
    public map<String, Contact> getContacts(List<String> mails){
        
        return getContactsByMap([
            SELECT Id, Email
            FROM Contact
            WHERE Email IN :mails
        ]);
    }
    
    /** Retourne les contacts de la liste sous forme de map<mail, contact> **/
    public map<String, Contact> getContactsByMap(List<Contact> contactsList){
        map<String, Contact> result = new map<String, Contact>();
        
        // Insère des contacts dans la map avec, en clé, l'adresse mail
        for(Contact myContact : contactsList){
            result.put(myContact.Email, myContact);
        }
        
        return result;
    }
}

IV. Test du trigger

Lors de toute mise en production, il est obligatoire que le code Apex ait une couverture de code d'au moins 75 % grâce à des tests unitaires sans erreur.

Je vous conseille tout de même de vous rapprocher le plus possible des 100 % de couverture afin de vous assurer qu'il y ait le moins de chances possible de rencontrer des bogues.

Pour effectuer des tests unitaires, nous allons devoir reproduire l'environnement complet qui va déclencher notre trigger. Il nous faudra donc :

  • une liste de cases ;
  • une liste de contacts (pour couvrir le cas où un case détient une adresse mail correspondant à un contact déjà existant dans Salesforce).

Nous allons devoir tester deux cas :

  • l'adresse mail du case correspond à un contact existant ;
  • l'adresse mail du case ne correspond pas à un contact existant.

Voici la classe de tests que j'ai développée pour couvrir le trigger :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
@isTest(seeAllData=false)
private class JoinContactToCaseTest {

    public static List<Contact> contactsList;
    public static List<Case> casesList;
    public static String email1;
    
    static void init(){
        contactsList = new List<Contact>();
        casesList = new List<Case>();
        
        email1 = 'toto@toto.com';
        
        casesList.add(new Case(
            SuppliedEMail = email1
        ));
    }

    /** Test avec un contact existant **/
    static testMethod void testWithExistingContact() {
        init();
        Test.startTest();
        
        contactsList.add(new Contact(
            Email = email1,
            LastName = email1
        ));
        insert contactsList;
        
        insert casesList;
        
        // Récupération du case pour être sûr d'avoir le champ 'ContactId' non null
        casesList = [
            SELECT Id, ContactId
            FROM Case
            WHERE Id = :casesList[0].Id
        ];
        
        // Vérification que le case est relié au bon contact
        System.assertEquals(casesList[0].ContactId, contactsList[0].Id);
        
        Test.stopTest();
    }
    
    /** Test avec un contact inexistant **/
    static testMethod void testWithDoesntExistingContact() {
        init();
        Test.startTest();
        
        insert casesList;
        
        // Récupération du case pour être sûr d'avoir le champ 'ContactId' non null
        casesList = [
            SELECT Id, ContactId
            FROM Case
            WHERE Id = :casesList[0].Id
        ];
        
        // Récupération du contact créé avec le trigger
        contactsList = [
            SELECT Id, Email
            FROM Contact
            WHERE Email = :email1
        ];
        
        // Vérification que le case est relié au bon contact
        System.assertEquals(casesList[0].ContactId, contactsList[0].Id);
        
        Test.stopTest();
    }
}

La première ligne peut vous amener à vous poser des questions. Elle signifie qu'il s'agit d'une classe de tests, mais que nous n'utiliserons aucune vraie donnée de Salesforce (via la valeur false du paramètre seeAllData). Vous n'êtes pas obligé d'ajouter la parenthèse avec son contenu, c'est-à-dire que l'annotation @isTest suffit, mais dans cette situation, cela revient à notre cas présent qui précise de ne pas utiliser de vraies données. En revanche, vous pouvez être, dans certaines situations, obligé d'en utiliser de vraies, notamment lorsque vous avez besoin de manipuler des profils, des rôles, des recordTypes, etc. dans votre code. À ce moment-là, vous devrez initialiser ce paramètre à true.

Vous remarquerez que je précise que ma classe est privée, mais ce n'est qu'un détail sans importance, elle aurait pu être publique. Une autre chose à noter est que vous pouvez déclarer votre classe avec with(out) sharing class qui signifie que vous souhaitez utiliser ou non les règles de partage mises en place à l'intérieur de votre organisation. Par défaut, comme dans ce tutoriel, je ne le mets pas et cela équivaut à les utiliser donc, comme si je l'avais déclaré comme private with sharing class.

Allons maintenant un peu plus en profondeur dans le code, je déclare trois variables :

  • une liste de contacts ;
  • une liste de cases ;
  • une adresse mail.

Je pense qu'au vu du scénario, vous devez comprendre leur utilité.

IV-A. Méthode init

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
static void init(){
    contactsList = new List<Contact>();
    casesList = new List<Case>();
        
    email1 = 'toto@toto.com';
        
    casesList.add(new Case(
        SuppliedEMail = email1
    ));
}

Afin de factoriser mon code pour éviter la redondance, j'ai pris pour habitude de créer une méthode init() qui me sert à initialiser, comme son nom l'indique, mon environnement de tests, c'est-à-dire les données. Je l'appelle en début de chaque méthode de tests. Ici, elle crée un case, mais ne l'ajoute pas dans Salesforce.

Passons maintenant à nos deux tests unitaires qui, je le rappelle, auront pour objectif de tester le trigger dans le cas où un contact aurait la même adresse mail que le champ suppliedEmail du case et un autre cas où ce contact n'existerait pas.

IV-B. Premier cas de test

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
/** Test avec un contact existant **/
static testMethod void testWithExistingContact() {
    init();
    Test.startTest();
        
    contactsList.add(new Contact(
        Email = email1,
        LastName = email1
    ));
    insert contactsList;
        
    insert casesList;
        
    // Récupération du case pour être sûr d'avoir le champ 'ContactId' non null
    casesList = [
        SELECT Id, ContactId
        FROM Case
        WHERE Id = :casesList[0].Id
    ];
        
    // Vérification que le case est relié au bon contact
    System.assertEquals(casesList[0].ContactId, contactsList[0].Id);
        
    Test.stopTest();
}

Il nous faut, dans un premier temps, initialiser notre environnement de tests. Nous appelons pour cela la méthode init.

Nous avons ensuite recours à la méthode statique Test.startTest, mais également à Test.stopTest à la fin de notre test. Nous sommes obligés de faire cela, afin de spécifier que ce qui sera exécuté entre ces deux méthodes, ce seront des tests qui seront soumis aux limitations imposées par Salesforce (ce que je vous ai mentionné légèrement plus haut dans un chapitre précédent, nombre de requêtes SOQL, exécution du temps du script, etc.). Donc, toutes les initialisations de variables et d'environnements doivent se faire en amont, ce que j'ai fait en appelant la méthode init précédemment.

Je demande ensuite à Salesforce de créer un contact puis le case, ce qui aura pour conséquence de déclencher le trigger développé plus haut.

Le case créé doit être rattaché avec notre contact. Pour s'en assurer, nous récupérons le case que nous venons d'enregistrer, une fois le trigger exécuté.

Petite anecdote à savoir à ce sujet, avant d'utiliser insert, nous avons un tableau de cases qui ne possèdent pas de champ Id ; mais après cela, c'est le cas puisqu'ils existeront dans Salesforce. C'est pour cette raison que, lorsque j'écris ma requête SOQL, je peux me permettre d'utiliser ce champ, comme il n'est plus null. Et je préfère récupérer le case en question par mesure de sécurité, car il m'est déjà arrivé de me rendre compte que certains champs sont à null alors qu'ils auraient dû être initialisés. Donc, depuis, j'ai pris l'habitude de récupérer mes résultats.

Dernière étape, la vérification des valeurs. Nous utilisons la méthode System.assertEquals en lui transmettant les valeurs que nous voulons vérifier. Dans notre cas, il s'agit donc de l'identifiant du contact, à savoir le champ ContactId du Case et le champ Id du Contact.

Vous n'êtes pas obligé de vous assurer de la cohérence de vos données pour couvrir votre code et mettre en production, mais je vous rappelle que vous devez au minimum avoir une couverture de code de 75 % et sans erreur, et je pense que vous comprendrez qu'il est toujours plus sûr de vérifier que le script fait bien ce que l'on attend de lui.

Voici ce que vous devriez obtenir dans le cas où votre couverture de tests dépasse les 75 % et ne possède aucune erreur :

Image non disponible

En revanche, voici ce que vous obtiendrez dans le cas où votre test provoque une erreur :

Image non disponible

Dans le cas présent, pour obtenir cette erreur, j'ai modifié les valeurs qui sont vérifiées en spécifiant «  bla  » comme identifiant du contact du case, ce qui n'est donc absolument pas cohérent.

À noter que le trigger que nous testons s'appelle JoinContactToCase.

IV-C. Second cas de test

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
/** Test avec un contact inexistant **/
static testMethod void testWithDoesntExistingContact() {
    init();
    Test.startTest();
        
    insert casesList;
        
    // Récupération du case pour être sûr d'avoir le champ 'ContactId' non null
    casesList = [
        SELECT Id, ContactId
        FROM Case
        WHERE Id = :casesList[0].Id
    ];
        
    // Récupération du contact créé par le trigger
    contactsList = [
        SELECT Id, Email
        FROM Contact
        WHERE Email = :email1
    ];
        
    // Vérification que le case est relié au bon contact
    System.assertEquals(casesList[0].ContactId, contactsList[0].Id);
        
    Test.stopTest();
}

Notre second test permet de nous assurer que, si le champ SuppliedEmail du case ne correspond à aucun contact, le trigger le crée pour ensuite le rattacher au case approprié.

La méthode est assez similaire à la première, sauf que nous ne créons pas de contact. Nous insérons seulement notre case pour ensuite le récupérer, lui et le contact créé à l'occasion dans Salesforce.

La finalité est toujours la même, nous vérifions que le case est relié au bon contact.

Il existe de nombreuses méthodes pour vérifier ou non des valeurs ou exécuter un test sous certaines conditions. Nous pouvons, par exemple, nous assurer que deux valeurs sont différentes grâce à la méthode System.assertNotEquals, ou lancer l'exécution en tant qu'un utilisateur donné ou encore vérifier le message renvoyé par une exception à l'aide d'un try… catch. Si vous êtes curieux et que vous voulez en apprendre plus sur les tests unitaires de Salesforce, je vous invite à lire la documentation et je vous propose quelques liens plus bas dans la partie «  Ressources utiles  ».

V. Test d'un service web

J'ai eu la chance (ou la poisse, je ne me suis pas encore décidé) de travailler sur un projet où il y a eu l'utilisation d'un service web pour y récupérer des données. Et donc, qui dit service web, dit obligatoirement tests du service web. Nous allons pour cela utiliser ce que nous appelons des données mocks, ce qui signifie que nous n'allons pas tester le service web en lui-même, afin de ne pas dépendre de lui dans le cas où il ne répondrait pas ou ne renverrait pas ce que nous attendons, mais plutôt créer de fausses données qu'il aurait retournées. Je vous invite à lire mon précédent article qui traite sur la façon d'appeler un service web externe à partir de Salesforce : Appeler un webservice externe depuis Salesforce.

À propos de la méthode employée, je vais utiliser le fichier WSDL fourni dans l'article précédemment cité pour l'importer dans Salesforce, afin qu'il me génère le client. Et c'est ce client, finalement, que nous allons tester.

Deux méthodes s'offrent à nous que je vais détailler par la suite :

  • transmettre une ressource statique à Salesforce contenant des données retournées par le service web ;
  • créer nous-mêmes les données renvoyées par le service web et manipulées par le client Salesforce (WSDL2Apex).

V-A. Tester avec une réponse mock

Effectuer des tests unitaires en utilisant des données mocks évite de dépendre de ce que l'on devrait recevoir. Ainsi, dans notre cas, si le web service ne répond pas ou que les données sont différentes de ce qui est attendu, cela n'affectera pas notre test.

Nous allons ici recomposer les données que nous aurait renvoyées le service web une fois parsées par le client Salesforce WSDL2Apex.

Afin de connaître la forme des données que vous devez reconstituer, je vous invite à jeter un œil aux classes qui ont été générées par Salesforce lorsque vous avez importé votre WSDL.

L'objet que vous utiliserez sera de type Result. À vous de voir ce qu'il contient, vu que cela dépend de ce que vous renvoie le service web (une simple donnée ou une architecture de données). Celui utilisé pour ce tutoriel ne retourne seulement que la valeur de l'opération mathématique, soit un nombre de type Double. Reconstruire les données est donc ici assez simple.

Il n'est pas difficile d'implémenter une réponse mock. Pour cela, vous devez créer une classe qui implémente l'interface WebServiceMock et y développer une méthode doInvoke qui dispose de plusieurs paramètres, puis utiliser l'un d'entre eux (response) pour y stocker votre donnée mocks.

Voici la classe utilisée pour le service web :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
@isTest
global class CalculatorWebServiceMockDivide implements WebServiceMock {
   global void doInvoke(
           Object stub,
           Object request,
           Map<String, Object> response,
           String endpoint,
           String soapAction,
           String requestName,
           String responseNS,
           String responseName,
           String responseType) {
       
       calculatorWebService.divideResponse_element respElement = new calculatorWebService.divideResponse_element();
       respElement.Result = 5.0;
       response.put('response_x', respElement); 
   }   
}

Nous commençons par déclarer notre classe comme une classe de tests (annotation @isTest), puis nous créons un objet du type correspondant à la classe de réponse de la méthode du service web que nous appelons. Nous reconstituons ensuite les données qu'est censée retourner la méthode appelée via l'attribut Result de la classe. Puis nous finissons par attribuer notre objet de type Result à la map réponse au niveau de la clé response_x.

C'est tout ce qu'il faut effectuer pour créer une réponse mock dans Salesforce. Passons maintenant à notre classe de tests unitaires pour apprendre comment l'utiliser :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
static testMethod void testWithMock() {
     Test.startTest();
        
     Double numerator = 10.7;
     Double denominator = 12.9;
        
     // Remplace la réponse du webservice par la réponse mock
     Test.setMock(WebServiceMock.class, new CalculatorWebServiceMockDivide());
        
     MyWebServiceCalculatorCallout myWebServiceCalculatorCallout = new MyWebServiceCalculatorCallout();
        
     // Compare des valeurs
     System.assertEquals(5.0, myWebServiceCalculatorCallout.divide(numerator, denominator));
        
     Test.stopTest();
}

La méthode est assez semblable à ce que j'ai pu vous présenter au-dessus. Nous avons les méthodes Test.startTest et Test.stopTest en début et fin de la méthode pour délimiter la zone de test, mais ce qui change, c'est l'appel à la méthode Test.setMock en donnant en premier paramètre l'interface WebServiceMock (celle que notre service mock implémente), les paramètres que nous devons transmettre à la méthode d'appel du web service, ainsi qu'une instance de notre classe de données mocks. Cela signifie qu'à partir de ce moment-là, chaque appel au service web ne retournera uniquement que les données mocks que nous avons transmises à notre environnement. Nous terminons comme d'habitude par nous assurer de la valeur retournée, qui est 5.0 pour le test effectué.

Une fois le test passé, nous pouvons vérifier que tout s'est bien déroulé grâce aux logs. Voyez par vous-même (classe MyWebServiceCalculatorCallout) :

Image non disponible

V-B. Tester avec une ressource statique

Il existe un deuxième moyen pour tester un service web, qui consiste à utiliser une ressource statique qui détient le contenu retourné par l'appel à une méthode du web service. Je ne pense pas qu'il soit nécessaire que je vous explique comment uploader une ressource statique dans Salesforce, j'estime qu'il s'agit d'un prérequis. Vous pouvez récupérer ce fichier dans une archive contenant toutes les ressources que je vous mets à disposition à la fin de ce tutoriel dans la partie Conclusion.

s
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
/** Test avec une ressource statique **/
static testMethod void testWithStaticResource(){
    Test.startTest();
        
    Double numerator = 10.7;
    Double denominator = 12.9;
        
    // Crée la réponse mock
    StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
    // Utilise la ressource statique divideResult
    mock.setStaticResource('divideResult');
        
    // Attribution de la réponse mock
    Test.setMock(HttpCalloutMock.class, mock);
        
    MyWebServiceCalculatorCallout myWebServiceCalculatorCallout = new MyWebServiceCalculatorCallout();
        
    // Comparaison des valeurs
    System.assertEquals(5.0, myWebServiceCalculatorCallout.divide(numerator, denominator));
        
    Test.stopTest();
}

Je ne vais pas vous refaire la même description que d'habitude. Ce qui diffère ici, c'est que vous devez créer un objet de type StaticResourceCalloutMock et lui transmettre le nom de la ressource statique que vous voulez utiliser pour les tests, pour finir par faire utiliser la méthode Test.setMock en donnant, en premier paramètre, la classe HttpCalloutMock, ainsi que le mock en question en second paramètre.

VI. Conclusion

C'est ici que se termine ce tutoriel. J'espère vous avoir initié au minimum de ce que vous devez savoir pour effectuer des tests unitaires dans vos développements d'applications Salesforce.

Certes, les cas présentés ici sont relativement simples à couvrir et à tester, mais l'objectif est surtout de vous montrer comment procéder.

Choses promises, choses dues, je vous partage une archive contenant les ressources utilisées tout au long de ce tutoriel, que vous pouvez télécharger ici (ou consulter directement les sources sur Github ou Bitbucket), l'ensemble contient :

  • le trigger (JoinContactToCase) ;
  • la classe de tests du trigger (JoinContactToCaseTest) ;
  • le WSDL du service web (calculator.xml) ;
  • la réponse mock du service web (CalculatorWebServiceMockDivide) ;
  • la ressource statique représentant une réponse du service web (divideResult.xml) ;
  • la classe de tests du service web (CalculatorWebServiceTest) ;
  • la classe de résultats des données du service web (CalculatorWebService).

VII. Ressources utiles

VIII. Remerciements

Je tiens à remercier tous ceux qui m'ont aidé à mettre en œuvre ce tutoriel, je parle de :

  • Cedric Duprez ;
  • jacques_jean ;
  • vermine.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Aurélien Laval. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.