What would be the next step after implementing a process model? Of course, it is testing all the different flows in the process to ensure that things work properly before it can be deployed on the production system. Testing a process model manually may become a tedious task as it involves multiple steps which requires human and system intervention.  In the previous post, Venu has explained how to automate process model testing using the RESTful services of BPM public APIs.

These RESTful services are no longer available because from 731 SP10, OData services are introduced for BPM public APIs so that the customer gets the required services from BPM out of the box. This blog focuses on writing JUnit tests for automation of process model testing using various OData services for creating the process, completing the human task, sending message to BPM and check the status of process.


Prerequisites

  1. SAP BPM 7.31 SP10 or higher (Note that the availability of a particular OData service depends on the version of BPM that is being used.)
  2. JUnit 4.10 from eclipse
  3. Restfuse (1.2)open source library from eclipse http://developer.eclipsesource.com/restfuse/ . Download the binaries from http://download.eclipsesource.com/technology/restfuse/restfuse-1.2.0.zip
    Restfuse is a JUnit extension to test RESTful services. A Restfuse based test uses annotations to define RESTful service URL, request type, authentication, content and its type. It is not required to deploy the tests to the BPM server. The tests run like any other JUnit test in eclipse.
    Note: We need Restfuse library as OData is a type of RESTful API.

Writing JUnit Tests for process model

Let us consider the following process model for writing JUnit tests. The goal here is to test the process isolated from other systems(like Process Integration or PI in short) in order to clear the air in the BPM space. That means the automated activities are configured with mocked service interfaces and doesn’t interact with external systems. Note that the mocked service interfaces are used here to illustrate various type of tests that you can write. Mocking service interface is not a mandatory step to test the process. You can also test your process in an environment with real service interfaces in which case the type of tests that you will write may vary.

ProcessModel.PNG


Let us see how to write JUnit tests for the above process model. The following annotations are required at the JUnit class level.



@RunWith(HttpJUnitRunner.class)
public class TestBPMProcessModel {
 // PollState is used to check/poll the system every few seconds if an expected event has happened
    @Context
    private PollState pollState;
  /** The destination object holds the details about the server in which the process is  deployed
    and needs to be tested.**/
    @Rule
    public Destination destination = getDestination();
    @Context
// The Response object will be injected after every request sent to the BPM system.
    private Response response;
// The X-CSRF-Token is mandatory to make a POST request to the OData service
    private String xcsrfTokenValue;
  // Cookies are necessary to maintain the session between different requests/tests
    private List<String> cookiesList;
/**In order to have provide the headers or request parameter to the destination URL ,
    you have to define a function which will be called before executing each test.
For eg ; **/
    public Destination getDestination() {
        Destination destination = new Destination(this, "dsf:22");
        addHeaders(destination);
        addToPathSegment(destination);
        return destination;
    }
// Adding headers
    private void addHeaders(Destination destination) {
        RequestContext context = destination.getRequestContext();
        context.addHeader("Accept", "application/json");
        context.addHeader("Content-Type", "application/json");
        context.addHeader("x-csrf-token", xcsrfTokenValue);
        StringBuilder cookiesStringBuilder = new StringBuilder();
        for (String cookie : cookiesList) {
            cookiesStringBuilder.append(cookie.split(";")[0]);
            cookiesStringBuilder.append(",");
        }
        context.addHeader("Cookie", cookiesStringBuilder.toString());
    }
// Adding request parameter to the destination
    private void addToPathSegment(Destination destination) {
        RequestContext context = destination.getRequestContext();
        context.addPathSegment("process-id", processinstanceID);
    }
}


Step 1: Create a process instance

The first step is to create an instance of the process. As the OData service to start a process is a POST request, the x-csrf-token must be retrieved first. This can be done by specifying the header “X-CSRF-TOKEN” value as “Fetch”. The “order” parameter specifies the order in which the tests will be executed.

Test 1 : Get the X-CSRF-TOKEN

The OData service to get the start data of the process takes the following inputs: vendor name, DC name and the process name. You can also use the link http://host:port to derive the OData URL for a particular action.



@HttpTest(method = Method.GET,
  headers = { @Header(name = "Accept", value = "application/json"),
  @Header(name = "X-CSRF-TOKEN", value = "Fetch") },
  authentications = @Authentication(type = AuthenticationType.BASIC, user = "userid", password = "@@@@"),
  path = "/bpmodata/startprocess.svc/sap.com/demo~mdqp.bpm.businessprocess/mdqp_business_process/StartData", order = 1)
  public void getStartData() {
  com.eclipsesource.restfuse.Assert.assertOk(response);
  Map<String, List<String>> headers = response.getHeaders();
  xcsrfTokenValue = headers.get("x-csrf-token").get(0);
  List<String> cookiesList = headers.get("set-cookie");
  cookiesList.addAll(cookies);
  }




Test 2: Start the process

The next test starts the process with the necessary payload. The start event is defined with a custom event trigger having service operation with the following input data structure:


<complexType name="Customer">
<sequence>
<element maxOccurs="1" minOccurs="0" name="customerId" type="string"/>
<element maxOccurs="1" minOccurs="1" name="firstName" type="string">
</element>
<element maxOccurs="1" minOccurs="1" name="lastName" type="string">
</element>
<element maxOccurs="1" minOccurs="1" name="street" type="string">
</element>
<element maxOccurs="1" minOccurs="1" name="city" type="string">
</element>
<element maxOccurs="1" minOccurs="1" name="zipCode" type="string">
</element>
<element maxOccurs="1" minOccurs="1" name="country" type="string">
</element>
<element maxOccurs="1" minOccurs="1" name="creditLimit" type="double">
</element>
<element maxOccurs="1" minOccurs="1" name="currency" type="string">
</element>
<element maxOccurs="1" minOccurs="0" name="priority" type="string"/>
</sequence>
</complexType>




This test makes use of the same OData service as above except that this is a POST request.



@HttpTest(method = Method.POST,
  type = MediaType.APPLICATION_JSON,
  content = "{\"ProcessStartEvent\":{\"Customer\": {\"firstName\": \"Lavanya\",\"lastName\": \"Mothilal\",\"street\": \"BEML\",\"city\": \"Bangalore\",\"zipCode\": \"560066\",\"country\": \"India\",
  \"creditLimit\": \"10000.0\", \"currency\": \"USD\"}}}",
  path = "/bpmodata/startprocess.svc/sap.com/demo~mdqp.bpm.businessprocess/mdqp_business_process/StartData", order = 2)
  public void startMDQPProcess() {
  com.eclipsesource.restfuse.Assert.assertCreated(response);
  String s = response.getBody();
  HashMap obj = (HashMap) JSON.parse(s);
  processInstanceID = (String) ((HashMap) obj.get("d")).get("processInstanceId");
  }




Step 2: Complete the human task

Test 3: Get the task instance ID

The first step is to retrieve the task instance ID. As there is no direct way to get the task instance ID, the OData service to fetch all the non-completed tasks is used. So in order for the test to fetch the correct task instance ID, please ensure that there are no other running process/task in the system.

The @Poll annotation will retry the method twice with 5 seconds interval in order to ensure that the task is created .



@HttpTest(method = Method.GET,
  headers = { @Header(name = "Accept", value = "application/json") },
  path = "/bpmodata/tasks.svc/TaskCollection?$orderby=CreatedOn%20desc&$filter=Status%20ne%20%27COMPLETED%27", order = 3)
  @Poll(times = 2, interval = 5000)
  public void getAddCreditLimitTaskInstance() throws Exception {
  if (pollState.getTimes() == 2) {
  com.eclipsesource.restfuse.Assert.assertOk(response);
  String s = response.getBody();
  HashMap obj = (HashMap) JSON.parse(s);
  Object[] tasks = (Object[]) ((HashMap) obj.get("d")).get("results");
  HashMap latestTask = (HashMap) tasks[0];
  taskInstanceID = (String) latestTask.get("InstanceID");
  }
  }




Test 4: Claim the task

The task needs to be claimed before it can be completed. For simplicity purposes, the same user which is used to start the process is used for claiming and completing the task as well. But you may want to perform these actions using a different user so in that case, a separate test needs to be added to get the X-CSRF-TOKEN with the authentication annotation  (as shown in test 1).

As there is no content to be passed for claiming the task, it is passed as empty.


@HttpTest(method = Method.POST, content = "{empty}", path = "/bpmodata/tasks.svc/Claim?InstanceID='{taskInstanceID}'&$format=json", order = 4)
  public void claimAddCreditLimitTask() {
  com.eclipsesource.restfuse.Assert.assertOk(response);
  }




Test 5: Complete the task

The task can be completed by passing the necessary payload.


@HttpTest(method = Method.POST, type = MediaType.APPLICATION_JSON,
  content = "{\"DataStewardTaskOutput\":{\"creditLimit\": \"200.0\"}}",
  path = "/bpmodata/taskdata.svc/{taskInstanceID}/OutputData", order = 5)
  public void completeAddCreditLimitTask() {
  com.eclipsesource.restfuse.Assert.assertCreated(response);
  }




Step 3: Send message to IME

After completing the task, the process would wait at the Intermediate Message Event (IME) . As there is no external system involved here, a message must be sent explicitly via the test. The OData service to send message to BPM is available from 731 SP16.

The IME in this process is modeled with an event trigger named “BackendCallbackTrigger” and the type of service interface looks as shown below:


<xsd:complexType name="BackendCreateCustomerCallbackComplexType">
   <xsd:sequence>
       <xsd:element name="customerId" type="xsd:string"/>
   </xsd:sequence>
</xsd:complexType>




Test 6: Send message



@HttpTest(method = Method.POST, type = MediaType.APPLICATION_JSON, content = "{\"message\":{\"BackendCreateCustomerCallbackComplexType\":{\"customerId\":123}}}, order=6)",
path = "/bpmodata/messages.svc/sap.com/demo~mdqp.bpm.businessprocess/BackendCallbackTrigger/EventTrigger")
    public void sendBackendCallback() {
com.eclipsesource.restfuse.Assert.assertCreated(response);
}




This responsibility of this OData service is only to submit the message to BPM system. So all the processes that has events (either a start event or an IME) which are awaiting a message and has matching correlation condition may consume the message.


Step 4 : Process Completion

The final step is to ensure that the process has completed successfully.

Test 7: Verify the completion of the process




@HttpTest(method = Method.GET, headers = { @Header(name = "Accept", value = "application/json") }, path = "/bpmodata/processes.svc/ProcessInstance('{process-id}')", order = 7)
  @Poll(times = 3, interval = 10000)
  public void checkProcessCompletion() {
  if (pollState.getTimes() == 3) {
  com.eclipsesource.restfuse.Assert.assertOk(response);
  String s = response.getBody();
  HashMap obj = (HashMap) JSON.parse(s);
  String status = (String) ((HashMap) obj.get("d")).get("Status");
  Assert.assertEquals("COMPLETED", status);
  }
  }





To report this post you need to login first.

6 Comments

You must be Logged on to comment or reply to a post.

Leave a Reply