- 17. März 2017

Mock für Schnittstellen im APEX Umfeld

Fast jedem Entwickler ist es schon passiert: Die neu entwickelten Funktionalitäten können nicht endgültig getestet werden, da eine Schnittstelle zu einem anderen System:

  • noch nicht fertig entwickelt ist
  • auf dem Testsystem nicht zur Verfügung steht
  • einen veralteten Stand hat

Unter diesen Umständen ist an einen Integrationstest gar nicht zu denken. Aber muss es denn sein, dass sämtliche Salesforce- Tests von der Existenz einer Schnittstelle abhängig sind? Nein! Aus diesem Grund existiert in fast allen Programmiersprachen, die Webservice- Aufrufe bedienen können, die Möglichkeit der Mock- Erstellung.

Was ist ein Mock?

Unter einem Mock kann eine Imitation oder ein Stellvertreter für eine Schnittstelle verstanden werden. Dieser kann die Beantwortung von Webservice- Anfragen anstelle der echten Schnittstelle übernehmen. Im Grunde genommen ist ein Mock ein Methodenaufruf, der das gleiche Interface (Mitgabeparameter und Rückgabeparameter) besitzt, wie der eigentliche Schnittstellenaufruf, den er imitieren soll. Allerdings kann der Mock eine Programmlogik zum Füllen der Rückgabeparameter enthalten, die komplett losgelöst von der Schnittstelle selbst ist.

Wann und wo kommen Mocks zum Einsatz?

Die Antwort hierauf ist einfach: Beim Testen von Coding durch Unit Tests.

Unit Tests werden oft automatisiert und regelmäßig in eingeplanten Testläufen ausgeführt. Sie sollen dazu dienen, sämtliche Programmlogik mit ausgewählten Testdaten zu durchlaufen, um anschließend das Ist- mit dem Soll-Ergebnis zu vergleichen. Sie sind besonders gut dafür geeignet, um sicherzustellen, dass neue Programmänderungen nicht die Funktionalität anderer Programmteile beeinträchtigen. Diese Tests durchlaufen somit Logik, die direkt von den Entwicklern beeinflusst werden kann. Eine Schnittstelle hingegen ist etwas Fremdes von einem anderen System. Ob diese funktioniert und genau das tut wofür sie entwickelt wurde, liegt nicht in der Verantwortung der Unit Tests.

Durch einen Mock kann für die Unit Tests ein Idealzustand (eine funktionierende Schnittstelle) simuliert werden. Somit sind keine Tests beeinträchtigt, selbst wenn es die eigentliche Schnittstelle noch gar nicht gibt.

Wie funktionieren Mocks in APEX?

Am folgenden Beispiel, einem Webservice, der eine Addition zweier Zahlen vornimmt, werden die Bestandteile und die Funktionsweise eines Mocks erläutert.

Als erstes wird ein Schnittstellenaufruf benötigt, der durch einen Mock ersetzt werden kann. Das folgende Coding ist durch WSDL2APEX entstanden und wurde von Salesforce anhand einer WSDL generiert. Mehr Informationen zu WSDL2APEX können hier eingesehen werden.

 public AdditionWebService.String DoAddition(String Username, String Password, String Addent1, String Addent2) {
   AdditionWebService.DoAddition_element request_x = new AdditionWebService.DoAddition_element();
   request_x.Username = Username;
   request_x.Password = Password;
   request_x.Addent1 = Addent1;
   request_x.Addent2 = Addent2;
   AdditionWebService.DoAdditionResponse_element response_x;
   Map<String, AdditionWebService.DoAdditionResponse_element> response_map_x = new Map<String, AdditionWebService.DoAdditionResponse_element>();
   response_map_x.put('response_x', response_x);
   WebServiceCallout.invoke(
     this,
     request_x,
     response_map_x,
     new String[]{endpoint_x,
      'http://www.mindforce.de/webservices/addition/Additionserver.asmx/DoAddition',
      'http://www.mindforce.de/webservices/addition/Additionserver.asmx',
      'DoAddition',
      'http://www.mindforce.de/webservices/addition/Additionserver.asmx',
      'DoAdditionResponse',
      'AdditionWebService.DoAdditionResponse_element'}
   );
   response_x = response_map_x.get('response_x');
   return response_x.DoAdditionResult;
 }

Diese DoAddition Methode kann nun in Salesforce überall aufgerufen werden.

public Integer getAddResult(Integer Addent1, Integer Addent2)
{ 
  AdditionWebService.String result;
  Integer intResult;
  AdditionWebService.AdditionServerSoap addServerSoap = 
                                        new AdditionWebService.AdditionServerSoap(); 
  result = addServerSoap.DoAddition(Params.instance.userName, Params.instance.password, addent1, addent2); 
  intResult = (Integer)result;
  return intResult;
}

Ein ganz normaler Unit Test für die gerade gezeigte getAddResult Methode würde nun bei jedem Durchlauf die Schnittstelle auslösen. Sollte die Schnittstelle nicht erreichbar sein bricht der Test ab.
Um dieses Verhalten zu vermeiden wird ein Mock für diesen Schnittstellenaufruf erstellt.


@isTest
global class MockWebServiceResponseGenerator implements WebServiceMock {
    public MockWebServiceResponseGenerator() {
        
    }
  
    global void doInvoke( Object stub, Object request, Map response, String endpoint,
                          String soapAction, String requestName, String responseNS,
                          String responseName, String responseType) 
    {
      if(request instanceOf AdditionWebService.DoAddition_element) {
        AdditionWebService.DoAddition_element workflowRequest = 
                                          new AdditionWebService.DoAddition_element();
        
        AdditionRequest = (AdditionWebService.DoAddition_element) request;
        String username = workflowRequest.Username;
        String passwd = workflowRequest.Password;
        String addent1 = workflowRequest.addent1;
        String addent2 = workflowRequest.addent2;

        AdditionWebService.DoAdditionResponse_element responseElement = 
                                  new AdditionWebService.DoAdditionResponse_element();
                
        //Call dummy method, that simulates the logic behind the webservice call
        responseElement.DoAdditionResult = dummyDoAdditionCallout(addent1, addent2);
        response.put('response_x', responseElement);
      }
    }

    // dummy method, that simulates the functionality behind the webservice call
    private DQSWebService.ArrayOfArrayOfString dummyDeleteAccountCallout(String addent1, String addent2) 
    {
      AdditionWebService.String result = new AdditionWebService.String();
      Integer add_result = (Integer)addent1 + (Integer)addent2;
      result = add_result;  
      return result;
    }
}

Mocks sind normale APEX Klassen, die das Interface WebServiceMock implementieren. Sie sind immer gleich aufgebaut und können, da sie nur zum Testen dienen, mit der Annotation @isTest versehen werden, um nicht die Salesforce Code Limits zu schmälern. Neben einem Constructor enthalten sie eine doInvoke Methode. Diese wird später dafür zuständig sein, den entsprechenden Webservice Call zu identifizieren und zu behandeln. In diesem Beispiel wird nach dem Typ AdditionWebService.DoAddition_element abgefragt, um zu identifizieren, ob der auszuführende Webservice Call der Additions Service ist. Anschließend werden die Parameter ausgelesen und an eine Dummy Methode übergeben. Die Aufgabe dieser Methode ist es, die Logik hinter dem Webservice zu simulieren. In diesem Fall ist es eine einfache Addition. Dummy Methoden können auch nur aus einer simplen Rückgabe von vorgefertigten Strings bestehen, ohne irgendwelche Logik zu enthalten. Sie sollen schließlich nur für Unit Tests dienen und nicht die Webservice Logik an sich testen.
Der erstellte Mock muss nun noch in die Unit Tests eingebunden werden.

Wie wird ein Mock in einen Unit- Test eingebunden?


@isTest static void testAddition() 
{
  Test.startTest();
  msCalculate calcClass = new msCalculate();
  Test.setMock(WebServiceMock.class, new MockWebServiceResponseGenerator());
  System.assertEquals(calcClass.getAddResult(1, 2), 3)
  Test.stopTest();
}

Durch das Bekanntmachen des Mocks in der Testmethode anhand des Test.setMock Aufrufs, werden alle Schnittstellenaufrufe, die aus dieser Methode abgesetzt werden, an die dort benannte Mock Klasse weitergeleitet. In diesem Beispiel gibt es nur einen Webservice. Jedoch kann hier durchaus in Zukunft noch eine Subtraktion, eine Multiplikation oder eine Division implementiert werden. All diese Calls können über den gleichen Mock abgearbeitet werden. Hierbei muss lediglich in der doInvoke Methode der Mockklasse die Unterscheidung erweitert werden und für die anderen Rechenoperationen entsprechende Dummy- Methoden implementiert werden. Somit muss nicht für jede Schnittstelle ein eigener Mock gebaut werden. Es ist jedoch ratsam zumindest pro Drittsystem, mit dem kommuniziert wird, eine eigene Mock- Klasse zu implementieren, um Verantwortlichkeiten klar voneinander zu trennen.

Setzen Ihre Entwickler Mocks bereits effektiv ein?
Salesforce Senior Consultant Martin Seehase

Haben Sie Fragen oder Anregungen? Verfassen Sie einen Kommentar oder melden Sie sich per Mail bei info@mindsquare.de. Unser Team wird Ihnen schnellstmöglich antworten.

Mein Name ist Martin Seehase und ich bin begeisterter SAP Consultant bei mindsquare. Wie meine Kollegen habe ich mein Hobby zum Beruf gemacht.

Sie haben Fragen? Kontaktieren Sie mich!





Schreiben Sie einen Kommentar

Bitte füllen Sie alle mit * gekennzeichneten Felder aus. Ihre E-Mail Adresse wird nicht veröffentlicht.