Overcome the Biggest Barriers to Digital Transformation

Discover 3 factors you can’t ignore that drive change in businesses
Icon Close DOWNLOAD EBOOK

Why should I test my process models? The short answer is: From a technical point of view, BPMN is a programming language. Therefore, diagrams should be treated like code.

In this blog series “Treat your processes like code – test them!” we’ll step through best practices and procedures to simplify testing and take a closer look at many of the available libraries, including:

When testing models, you’re likely to have questions arise such as:

  • When and how do I use these different libraries?
  • What exactly should be tested – the model and/or the executed code?
  • How do I deal with dependencies to other models?
  • How do I measure my test coverage?

At this point, we would like to recommend the Camunda Best Practices on Testing. In the first posts of this series, we will mainly focus on the first testing scope and write unit tests with Java.

Today’s topic is: Testing Entire Process Paths

The implementation for this post can be found in this GitHub repository.

Let’s take a look at the following order fulfillment process:

“Entire process paths” means testing from start to finish. With complex models, we have often seen that test cases only cover certain parts. In our process, an example would be to test order cancellation separately. The reason you might want to take this approach is because the sequences before and after have already been tested, or the individual test cases become larger and more complex to modify

However, you should always test entire process paths to make sure that:

  • Dependencies in the process are taken into account: Changes to elements that are executed before or after can have side effects. This is especially true for changes to variables that are required for processing.
  • Definition of test cases is simplified: This sounds contradictory at first glance. But we’ve found that considering the whole process scenario simplifies the definition of test cases. Especially when considering only the differing behavior of activities and data in alternative scenarios.
  • Adjustments in the process become easier: Because the entire process is considered, adjustments to the process can be made with greater certainty.

There are, however, cases where it makes sense to test individual activities of a process. An example of this are reusable components. For example, the task “Send cancellation” could be a reusable service task for sending e-mails. However, it should not only be tested in the process for order fulfilment, but also in a separate scope.

With the camunda-bpm-assert library these small, reusable components can be tested very easily. However, testing more complex processes leads to redundant or unclear code. Therefore, the camunda-bpm-assert-scenario is available for testing entire process paths. Now, we will look at a method to test complete process paths easily and efficiently with this library. If you don’t know this project yet, you can have a closer look at the GitHub repository.

Defining the default behavior: First, a default behavior is defined for all elements. This also applies to tasks that are not on the “happy path”, such as the “Cancel Order” task. This means that only the differences in behavior needs to be specified in the individual scenarios.

@Before
public void defaultScenario() {
    MockitoAnnotations.initMocks(this);
    Mocks.register("sendCancellationDelegate", new SendCancellationDelegate());

    //Happy-Path
    when(testOrderProcess.waitsAtUserTask(TASK_CHECK_AVAILABILITY))
            .thenReturn(task -> {
                task.complete(withVariables(VAR_PRODUCTS_AVAILABLE, true));
            });

    when(testOrderProcess.waitsAtUserTask(TASK_PREPARE_ORDER))
            .thenReturn(TaskDelegate::complete);

    when(testOrderProcess.waitsAtUserTask(TASK_DELIVER_ORDER))
            .thenReturn(task -> {
                task.complete(withVariables(VAR_ORDER_DELIVERED, true));
            });

    //Further Activities
    when(testOrderProcess.waitsAtUserTask(TASK_CANCEL_ORDER))
            .thenReturn(TaskDelegate::complete);
}


Identifying activities with different outputs: This step is about identifying activities that provide alternative outputs so that the process takes different paths depending on the output. Often these activities are located before Inclusive or Exclusive Gateways. In our example, this applies to two tasks.

The “Deliver order” activity even has two additional scenarios.

  • The order could not be delivered successfully and it is retried the next day
  • Delivery is not possible and the order must be cancelled.

After the different scenarios have been identified, the specific test cases can now be implemented.

Implementing the test cases: To keep the implementation as simple as possible, only variables relevant to the process flow are considered.

Happy Path
For this purpose, you only have to start the scenario. Afterwards it can be checked whether certain elements or end events have been completed.

@Test
public void shouldExecuteHappyPath() {
    Scenario.run(testOrderProcess)
            .startByKey(PROCESS_KEY)
            .execute();

    verify(testOrderProcess)
            .hasFinished(END_EVENT_ORDER_FULLFILLED);
}

Send Cancellation
We need to override the behavior of the “Check availability” task:

@Test
public void shouldExecuteCancellationSent() {
    when(testOrderProcess.waitsAtUserTask(TASK_CHECK_AVAILABILITY)).thenReturn(task -> {
        task.complete(withVariables(VAR_PRODUCTS_AVAILABLE, false));
    });

    Scenario.run(testOrderProcess)
            .startByKey(PROCESS_KEY)
            .execute();

    verify(testOrderProcess)
            .hasFinished(END_EVENT_CANCELLATION_SENT);
}

Cancel Order
To test this, we need to throw an error in the “Deliver Order” task instead of completing it:

@Test
public void shouldExecuteOrderCancelled() {
    when(testOrderProcess.waitsAtUserTask(TASK_DELIVER_ORDER)).thenReturn(task -> {
        taskService().handleBpmnError(task.getId(), "OrderCancelled");
    });

    Scenario.run(testOrderProcess)
            .startByKey(PROCESS_KEY)
            .execute();
            
    verify(testOrderProcess)
            .hasCompleted(TASK_CANCEL_ORDER);
    verify(testOrderProcess)
            .hasFinished(END_EVENT_ORDER_CANCELLED);
}

Deliver twice
To execute the loop with the timer event and then complete the process, we need to define two different scenarios for the “Deliver Order” task:

@Test
public void shouldExecuteDeliverTwice() {
    when(testOrderProcess.waitsAtUserTask(TASK_DELIVER_ORDER)).thenReturn(task -> {
        task.complete(withVariables(VAR_ORDER_DELIVERED, false));
    }, task -> {
        task.complete(withVariables(VAR_ORDER_DELIVERED, true));
    });

    Scenario.run(testOrderProcess)
            .startByKey(PROCESS_KEY)
            .execute();

    verify(testOrderProcess, times(2))
            .hasCompleted(TASK_DELIVER_ORDER);
    verify(testOrderProcess)
            .hasFinished(END_EVENT_ORDER_FULLFILLED);
}

Conclusion

With the camunda-bpm-assert-scenario library, it is very easy to test entire process paths. With the approach described above, the defined tests can be implemented efficiently and clearly. But what about code dependencies or integrated call activities? Should these be tested as well or should they be hidden with Mocking Frameworks? We will address this topic in the next post. Stay tuned!

If you’d like to dive deeper into testing topics, check out Testing Cheesecake – Integrate Your Test Reports Easily with FlowCov, presented at CamundaCon LIVE 2020.1 and available free on-demand.

Dominik Horn is Co-Founder of FlowSquad — specialists in process automation and individual software development. Stay in touch with Flowsquad on TwitterGitHub or email — info@flowsquad.io.

  • Camunda Cloud Zeebe K8s Operator

    When working with Cloud Native applications on top of Kubernetes, you might need to make some tough decisions about where to run the services and infrastructure that your applications need, which will directly impact your maintenance costs.

    Read more
  • What You Should Know About Using Camunda...

    Camunda Platform Run, or Camunda Run for short, is a lightweight distribution of Camunda Platform that works straight out-of-the-box and comes pre-configured for production environments. Camunda Run is an excellent option if you’re just getting started with Camunda, or if you’re ready to use Camunda Platform in production. You can download the open-source Community Edition for free, and easily upgrade to the Enterprise Edition to access more features and support options. After you download and extract Camunda Run, you’ll find a configuration folder that contains a file called production.yml. The properties defined in this file are important for production environments and are derived from our guide to securing your Camunda Platform installation. To start Camunda Run with the production.yml configuration,...

    Read more
  • BPM meeting

    Why You’re Struggling with Your BPM Suite

    Are you using a business process management (BPM) suite such as IBM Business Process Manager, Pega, Appian, jBPM, or Bonitasoft? If so, do any of these problems sound familiar? The software was hard to install, and we need specialized knowledge to maintain it We haven’t been able to fully integrate the software with other parts of our tech stack It’s hard to find developers who know how to make the software do what we need it to do Our developers have to work around the software’s technical or functional limitations These problems stem from the fact that many BPM suites are built on a closed architecture and take a proprietary approach to application development and process automation. That’s not just...

    Read more